home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
yerk
/
mps231ss.hqx
/
Mops source
/
Nuc source
/
Handlers.asm
< prev
next >
Wrap
Assembly Source File
|
1993-02-25
|
167KB
|
7,035 lines
; =======================================
; COMPILATION HANDLERS
; =======================================
; This is our code generator. HANDLERS is the entry point. We have only
; one, since A5 has to be saved and restored, and this simplifies things.
;
; Entered with:
;
; D0 = selector for handler routine to be executed
; D1 = saved A5 (modbase)
; D2 = opcode for ->, ++> etc.
;
; Individual routines may have other parameters on the stack.
; A compilation handler is called during compilation to compile the machine
; code for a Mops word. We do things this way since words compile into a
; variety of different machine code sequences. The handler code
; immediately follows the header of the word. This is the same code as is
; passed here in D0.
; Just in front of the handler routine itself we put a 2-byte value which gives
; the offset from the cfa (= compilation address) of the word being compiled
; to its "body". This is a varying quantity in this implementation, and this
; scheme allows >BODY to get the right answer. The HNDLR macro puts this
; 2-byte quantity in.
; We take the opportunity to include some local optimization.
; This is the best kind for Forth/Mops, where the programmer (rightly) has
; responsibility for higher-level efficiencies. The optimization we do
; is aimed at speeding common sequences, rather than picking up everything
; under the sun.
hbase equ *+32766
jtbl handlers
ref comma,wcomma,ncomma,swap,pcomp,length,doPAtAbs,star,slash
_debugger trapw $a9ff
ipath "::mops source:nuc source:"
incl "OD.asm"
; ============================
; MACROS etc.
; ============================
; Flag codes for various word types. The actual values are quite
; arbitrary.
inline equ 0
docode equ 1
docol equ 3
docon equ 5
doval equ 6
dovbl equ 8
dovec equ 9
spec equ 10
inlinex equ 11
InstMk_con equ $4AFC
HNDLR macrox &1,&2 ; label, >body offset
dc.w call_h-hbase
dc.w &2
&1
endm
;Push and pop macros - modified to use A6 instead of A7.
push.b macrox &1
move.b &1,-(a6)
endm
push.w macrox &1
move.w &1,-(a6)
endm
push macrox &1
move.w &1,-(a6)
endm
push.l macrox &1
move.l &1,-(a6)
endm
pop.b macrox &1
move.b (a6)+,&1
endm
pop.w macrox &1
move.w (a6)+,&1
endm
pop macrox &1
move.w (a6)+,&1
endm
pop.l macrox &1
move.l (a6)+,&1
endm
compyl macrox &1
LEA &1-hbase(A4),A0
PUSH.L A0
JSR pcomp
endm
; Note: here we can only call pcomp with inline words, and then pcomp
; doesn't call us back. This is essential - Handlers is definitely not
; reentrant!
INL macrox &1
dc.w .xx-*-2
&1 &1_m
.xx
endm
CODE macrox &1,&2,&3 ; label, flag, opt
if &2 = inline
inl &1
else
error "Only inline allowed on definitions in Handlers"
endi
endm
NOHEAD macrox &1,&2 ; label, flag
code &1,&2
endm
GET.L macrox &1,&2
MOVE.L &1,A1
MOVE.L (A1),&2
endm
GETA macrox &1,&2
MOVE.L &1,&2
endm
GET.W macrox &1,&2
MOVE.L &1,A1
MOVE.W (A1),&2
endm
GET.B macrox &1,&2
MOVE.L &1,A1
MOVE.B (A1),&2
endm
PUT.L macrox &1,&2
MOVE.L &2,A1
MOVE.L &1,(A1)
endm
PUT.W macrox &1,&2
MOVE.L &2,A1
MOVE.W &1,(A1)
endm
PUT.B macrox &1,&2
MOVE.L &2,A1
MOVE.B &1,(A1)
endm
INC.L macrox &1,&2
MOVE.L &2,A1
ADD.L &1,(A1)
endm
pushop macrox &1
PUSH.W &1-hbase(A4)
CLR.W -(A6)
endm
compop macrox &1
pushop &1
JSR wcomma
endm
compopl macrox &1
push.l &1
jsr comma
endm
; ============================
; ENTRY POINT
; ============================
Handlers
LEA hbase,A0
MOVEM.L D1-D7/A2/A4,savedRegs-hbase(A0)
; Save regs we need
; -- D1 is actually the A5 value
MOVE.L (A7)+,savedRA-hbase(A0) ; and rtn addr
MOVE.L A0,A4 ; A4 is now our base register
NEG.W D0
LEA htable,A0
MOVE.W 0(A0,D0.W),D0
JSR 0(A4,D0.W)
hndExit MOVEQ #0,D0 ; Normal return point
hndErr ; We return here on an error, with
; err# in D0
MOVEM.L savedRegs,D1-D7/A2/A4
MOVE.L D1,A5 ; Restore regs
MOVE.L savedRA,A0
JMP (A0)
savedRegs
savedA5 long ; D1 (really A5 = modbase)
opcode long ; D2 = opcode
long 5 ; D3-D7
long ; A2
savedA4 long ; A4
savedRA long
htable dc.w hsetup-hbase,call_h-hbase,const_h-hbase
dc.w val_h-hbase,create_h-hbase,vect_h-hbase,pm_h-hbase
dc.w at_h-hbase,store_h-hbase,call_h-hbase,reg_h-hbase
dc.w obj_h-hbase,does_h-hbase,loc_h-hbase
dc.w LitAddr-hbase,pushDesc_h-hbase,dummy-hbase
dc.w Literal-hbase,CompExit-hbase
dc.w CompJSRLong-hbase,pif-hbase,compPlLoop-hbase
dc.w hmentry-hbase,hplentry-hbase,heb-hbase
dc.w hStkObj-hbase,hDoEx-hbase,hgenaddr-hbase,hgenxaddr-hbase
dc.w class_h-hbase,compimp-hbase,objPtr_h-hbase,bit_h-hbase
dc.w swap_h-hbase,hLoadBA-hbase,FixDoes-hbase,hPatch-hbase
dc.w Floc_h-hbase,Fcon_h-hbase,Fval_h-hbase
dc.w FP1_h-hbase,FP2_h-hbase,FPcmp_h-hbase,hCompFPUL-hbase
dc.w FCRcon_h-hbase,class_in_mod_h-hbase,imported_h-hbase
dc.w hColA-hbase,shift_h-hbase,hDefnEnd-hbase,Fat_h-hbase
dc.w Fst_h-hbase,builds_h-hbase,MultDiv_h-hbase
dummy dc.w $FFEF ; Unassigned handler code
; ===============================
startGlobs
dp long
fmkCnt long
callOut long
CCmpFlg long
colaFlg long
optq long
methodq long
numPL long
numP long
numF long
FltFlg long
locNo long
localq long
numLast long
modEntry long
saveTandS long
xJsrToVect long
xAtAbs long
xMulX long
xPushBool long
MBcomp long
SAcomp long
HWPavail long
state long
UseFPUq long
ptrFPdisp long
ptrFPdisp2 long
ptrFPnew long
ptrFPULit long
ptrLfloat long
ptrToLfloat long
ptrToFval long
ptrLFdisp long
ExtraLocals long
HeldMod long
EBmod long
MethIndex long
inhibitMBq long
OurGlobs
hsetup MOVE.L (A7)+,A0 ; Save return addr
MOVE.L (A7)+,D2 ; and another rtn addr from higher up
LEA OurGlobs,A1
MOVEQ #(ourGlobs-startGlobs)/4-1,d0 ; #globs - 1 -- keeps changing!
.suLoop MOVE.L (A7)+,-(A1)
DBRA D0,.suLoop
move.l d2,-(a7) ; Restore return addrs
move.l a0,-(a7)
moveq #-1,d0
put.l d0,MBcomp
lea XLOD,a0
bsr ClearOD
push.l ExtraLocals
bsr SetAddr
moveq #0,d0
bsr LoadBase
move.l opDispl,XLdispl-hbase(a4)
moveq #0,d0
bsr EAbits
move.w d0,XLeaBits-hbase(a4)
lea OD,a1 ; Return OD addr for main prog
rts
; ============================
; INSTRUCTIONS FOR COMPILATION
; ============================
xpush PUSH.L 2(A3)
xpushi PUSH.L #0
xpushD0 PUSH.L D0
xpushD1 PUSH.L D1
xpushD2 PUSH.L D2
xpushA0 PUSH.L A0
xpopD0 POP.L D0
xpopD2 POP.L D2
xpopA0 POP.L A0
xpopA1 POP.L A1
xpopD7 POP.L D7
xTSTstk move.l (A6),d0
xTSTstkPop move.l (A6)+,d0
xmvA2D0 move.l a2,d0
xmvD0D2 move.l d0,d2
xmvD2D0 move.l d2,d0
xmvD0A0 move.l d0,a0
xmvD0A1 move.l d0,a1
xmvA1D2 move.l a1,d2
xmvD0A2 MOVE.L D0,A2
xmvA2A0 MOVE.L A2,A0
xmvD1A0 MOVE.L D1,A0
xmvD1stk MOVE.L D1,(A6)
xMv2ndD0 move.l 4(a6),d0
xRpshA0 MOVE.L A0,-(A7)
xMentry MOVE.L A2,-(A7)
MOVE.L A0,A2
xRpopA2 MOVE.L (A7)+,A2
xRpshD7 MOVE.L D7,-(A7)
xRpopD7 MOVE.L (A7)+,D7
xRpshA5 move.l a5,-(a7)
xRpopA5 move.l (a7)+,a5
xPopRpsh MOVE.L (A6)+,-(A7)
xmvA3D0 MOVE.L A3,D0
xmoveEA MOVE.L 2(A3),D0
x2ndToA0 MOVE.L 4(A6),A0
xD2ByA0 MOVE.L D2,(A0)
xpopByA0 POP.L (A0)
xJsrA0 JSR (A0)
xMMtoR MOVEM.L D4-D7,-(A7)
xMMfrR MOVEM.L (A7)+,D4-D7
xMMpop MOVEM.L (A6)+,D5-D7
xChnSub SUB.L (A6)+,D1 ; These 2 must go together
xNegD1 NEG.L D1
xSubD1 SUB.L (A6),D1
xExtD1 ext.w d1
ext.l d1
xRTS RTS
xTSTA0 MOVE.L A0,D0 ; Can't do a TST on A0, but this MOVE has
; the same effect on the CC!
xAddD1A0 add.l d1,a0
xAddStkA0 add.l (a6)+,a0
xgxself ADD.W -2(A0),A0
ADD.W -2(A0),A0
; ADDQ #4,A0
xclr CLR.L -(A6)
xclrD0 CLR.B D0
xbsr BSR .dummy
xFPmove movem.l (a0),d0-d2
movem.l d0-d2,(a1)
xFPmove2 movem.l (a1),d0-d2
movem.l d0-d2,(a0)
xsubStk SUB.L D0,(A6)
xadds ADD.B D0,D0
SUB.B D0,D0
AND.B D0,D0
OR.B D0,D0
EOR.B D0,D0
CMP.B D0,D0
NOP
NEG.B D0
NOT.B D0
xaddq ADDQ.B #8,D0
SUBQ.B #8,D0
xaddi dc.w $0600,$0400,$0200,$0000,$0A00,$0C00
xcmp CMPM.L (A6)+,(A6)+
xcmpD2 CMP.L (A6),D2
; Floating point ops
dc.w $F200,$0038 ; FCMP
xFPops dc.w 0,0 ; FP move is handled elsewhere
dc.w $F200,$0022 ; FADD
dc.w $F200,$0023 ; FMUL
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w $F200,$0028 ; FSUB
dc.w $F200,$0020 ; FDIV
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w 0,0
dc.w $F200,$0018 ; FABS
dc.w $F200,$001A ; FNEG
dc.w $F200,$000E ; FSIN
dc.w $F200,$001D ; FCOS
dc.w $F200,$000F ; FTAN
dc.w $F200,$000A ; FATAN
dc.w $F200,$0004 ; FSQRT
; SANE codes
xSANE dc.w $0000 ; FADDX
dc.w $0004 ; FMULX
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w $0002 ; FSUBX
dc.w $0006 ; FDIVX
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w 0
dc.w $003E ; Special code for ABS - we don't need
; to call SANE
dc.w $003F ; NEG ditto
dc.w $0018 ; FSINX
dc.w $001A ; FCOSX
dc.w $001C ; FTANX
dc.w $001E ; FATANX
dc.w $0012 ; FSQRTX
; Table to convert from integer condition encodings to the equivalent
; floating-point codes.
int2FPconditions
dc.b 0 ; Undefined
dc.b 0 ; Undefined
dc.b 0 ; HI has no equivalent
dc.b 0 ; LS has no equivalent
dc.b 0 ; HS has no equivalent
dc.b 0 ; LO has no equivalent
dc.b $E ; NE
dc.b $1 ; EQ
dc.b 0 ; Undefined
dc.b 0 ; Undefined
dc.b 0 ; Undefined
dc.b 0 ; Undefined
dc.b $13 ; GE
dc.b $14 ; LT
dc.b $12 ; GT
dc.b $15 ; LE
; =========================
; FLAGS etc.
; =========================
ObjPtr long
ivPtr long
methCfa long
CMPdesc long
FetchSize dc.b 2
ForceToR byte
svForceToR byte
InhibitClr byte
svInhibitClr
byte
Rcond byte
Flocq byte
FatStq byte
Condition byte
WhichA byte
FPA byte
FPdispFlg byte
DPbacked byte
ChnReg byte
align
.dummy
; ===========================
; UTILITY SUBROUTINES
; ===========================
FlushCache
loc
movem.l d0/d1/a0/a1,-(a6) ; Save regs
get.b HWPavail,D0
beq.s .out ; Out if HWPriv trap not available
moveq #1,D0 ; Code 1 means flush the instrn cache
dc.w $A198 ; HWPriv trap
.out movem.l (a6)+,d0/d1/a0/a1 ; Restore regs
rts
; LowBit finds the bit number of the lowest-order "1" bit in D1.
; Returns bit number in D0, and CC as follows:
; EQ = D1 was zero (D0 will be unchanged in this case)
; LT = Just one bit was set
; GT = More than one bit was set
;
; D1 is preserved.
LowBit loc
tst.l d1
beq.s .lbOut ; If D1 is zero, straight out with "EQ"
push.l d1 ; Save D1
moveq #31,d0
.lp lsr.l #1,d1
dbcs d0,.lp
beq.s .oneBit
pop.l d1 ; Restore D1 before we set CC
sub.w #31,d0 ; Set D0 to bit number
neg.w d0
move #0,CCR ; Set "GT" (d0 may have been zero)
.lbOut rts
.oneBit pop.l d1
sub.w #31,d0
neg.w d0
move #8,CCR ; Set "LT"
rts
; BitReverse reverses the bits in the low word of D0. Used for compiling
; MOVEM and FMOVEM instructions. Leaves the top word of D0 unchanged.
BitReverse
movem.l d1/d2,-(a6)
moveq #15,d2
move.w d0,d1
move #4,ccr
.lp2 roxr.w #1,d0
roxl.w #1,d1
dbra d2,.lp2
move.w d1,d0
movem.l (a6)+,d1/d2
rts
WdChk loc
CMP.L #-32768,D0
BLT.S .out
CMP.L #32767,D0
BGT.S .out
MOVE #4,CCR ; Set "EQ"
.out RTS
ByteChk loc
CMP.L #-128,D0
BLT.S .out
CMP.L #127,D0
BGT.S .out
MOVE #4,CCR ; Set "EQ"
.out RTS
; ===========================
; OD STACK AND ASSOCIATED OPS
; ===========================
doBackDP
PUSH.L A1
PUT.L opDP,DP
ADDQ.B #1,DPbacked-hbase(A4)
POP.L A1
RTS
backDP macrox
BSR doBackDP
endm
markDP macrox
GET.L DP,opDP
endm
resetDP macrox
GET.L DP,opDP
CLR.W ODsize(A0)
endm
noOpt macrox
CLR.W OD-hbase(A4)
endm
upOD macrox
SUBI #ODsize,A0
endm
downOD macrox
ADDI #ODsize,A0
endm
UseODsrc macrox
bsr doUseODsrc
endm
OD byte ODsize
align
ODnew byte ODsize ; Used in generating new descriptors
align
ODsav byte ODsize ; Copy of OD for use here
align ; - saved over possible , and w,
ODprev byte ODsize
align
OD2back byte ODsize
align
OD3back byte ODsize
align
OD4back byte ODsize
align
ODdummy byte ODsize
align
dc.w 0 ; So if we do DownOD we don't get a valid desc
ODreg byte ODsize ; Used for intermediate temporary registers
align
ODsrc byte ODsize ; Used for temp source e.g. stack
align
ODpmSrc byte ODsize ; Used for stack source for arithmetic
align
ODdst byte ODsize ; Used for temp destination
align
XLOD byte ODsize ; Used for ExtraLocals area
align
tmpOD byte ODsize ; Used for anything else at the top level
align
ODstkSize equ 8 ; Max of 8 temp descriptors
byte (ODsize + 4) * ODstkSize
ODstk
ODsp long
MoveDesc
move.l (a0),(a1)
move.l 4(a0),4(a1)
move.l 8(a0),8(a1)
move.l 12(a0),12(a1)
move.l 16(a0),16(a1)
rts
doUseODsrc lea ODsrc,a0 ; Then fall thru to ClearOD
ClearOD
ClrOD
clr.l (a0)
clr.l 4(a0)
clr.l 8(a0)
clr.l 12(a0)
clr.l 16(a0)
move.b #1,opind
move.b #Lcode,opSize
rts
ExgOD
MOVEM.L A0-A1,-(A6)
MOVEQ #4,D0
.exdLp MOVE.L (A1),D1
MOVE.L (A0),(A1)+
MOVE.L D1,(A0)+
DBRA D0,.exdLp
MOVEM.L (A6)+,A0-A1
RTS
SaveOD
LEA OD,A0
LEA ODsav,A1
BSR.S MoveDesc
; Now fall through to InitODs
InitODs
LEA ODsrc,A0
bsr ClearOD
MOVE.B #stkPop,opMode
MOVE.B #Lcode,opSize
LEA ODnew,A0
bsr ClearOD
MOVE.B #stkPop,opMode
PUSH.L A1 ; Save A1 over GET - needed in places
get.L DP,opDP
POP.L A1
RTS
; PushOD pushes a new OD value. The new descriptor value is in ODnew, and
; goes to OD. The previous descriptor will have been saved in ODsav by a
; call to SaveOD, and goes to ODprev. The previous value of ODprev
; goes to OD2back, and so on. Keeping all these allows us to optimize
; sequences such as
; <value> 99 > IF ...
; ODnew is not changed, and A0 is left pointing there.
PushOD
GET.L optq,D0
BEQ.S .noOpt
LEA OD3back,A0
LEA OD4back,A1
BSR.S MoveDesc
LEA OD2back,A0
LEA OD3back,A1
BSR.S MoveDesc
LEA ODprev,A0
LEA OD2back,A1
BSR.S MoveDesc
LEA ODsav,A0
LEA ODprev,A1
BSR.S MoveDesc
LEA ODnew,A0
LEA OD,A1
bra.s MoveDesc
.noOpt CLR.W OD-hbase(A4)
RTS
PopOD
LEA ODsav,A0
LEA ODnew,A1
BSR.S MoveDesc ; ODsav -> ODnew
LEA ODprev,A0
LEA OD,A1
BSR.S MoveDesc
LEA ODsav,A1
BSR.S MoveDesc ; ODprev -> OD and ODsav
LEA OD2back,A0
LEA ODprev,A1
BSR.S MoveDesc ; OD2back -> ODprev
LEA OD3back,A0
LEA OD2back,A1
BSR.S MoveDesc ; OD3back -> OD2back
LEA OD4back,A0
LEA OD3back,A1
BSR.S MoveDesc ; OD4back -> OD3back
CLR.W (A0) ; Invalidate OD4back
LEA ODnew,A0
RTS
PopODts ; As for PopOD but saves the ts and flags fields from the
; descriptor pointed to by A0, and sets it into the
; corresponding fields of ODnew. Uses D0-1.
MOVE.W (A0),D0
MOVE.B opFlags,D1
BSR.S PopOD
MOVE.W D0,(A0)
MOVE.B D1,opFlags
RTS
DropOD ; As for PopOD but leaved ODnew unchanged. The main effect
; is thus to drop the ODsav descriptor.
LEA ODprev,A0
LEA OD,A1
BSR.S MoveDesc
LEA ODsav,A1
BSR.S MoveDesc ; ODprev -> OD and ODsav
LEA OD2back,A0
LEA ODprev,A1
BSR.S MoveDesc ; OD2back -> ODprev
LEA OD3back,A0
LEA OD2back,A1
BSR.S MoveDesc ; OD3back -> OD2back
LEA OD4back,A0
LEA OD3back,A1
BSR.S MoveDesc ; OD4back -> OD3back
CLR.W (A0) ; Invalidate OD4back
LEA ODnew,A0
RTS
NewOD ; Allocates a new desc off the OD stack. Copies the
; A0 desc into the new one. Leaves A0 pointing to the
; new one.
SUB.W #ODsize+4,ODsp-hbase(A4)
cmpi.w #-( ODStkSize * (ODsize+4) ),ODsp-hbase(a4)
ble .odOvfl
PUSH.L A1
LEA ODstk,A1
ADD.W ODsp,A1
MOVE.L A0,ODsize(A1)
BSR MoveDesc
MOVE.L A1,A0
POP.L A1
RTS
NewClrOD ; Allocates a new desc off the OD stack and clears it
; (except for the opind field, which is set to 1, and the
; opSize field which is set to Lcode).
; Leaves A0 pointing to the new one.
sub.w #ODsize+4,ODsp-hbase(A4)
cmpi.w #-( ODStkSize * (ODsize+4) ),ODsp-hbase(a4)
ble.s .odOvfl
push.l a1
lea ODstk,a1
add.w ODsp,a1
move.l a0,ODsize(a1)
move.l a1,a0
pop.l a1
bra ClearOD
ReleaseOD
LEA ODstk,A0
ADD.W ODsp,A0
MOVE.L ODsize(A0),A0
ADD.W #ODsize+4,ODsp-hbase(A4)
BGT.S .odUndfl
RTS
.odUndfl ; OD stack underflow
dc.w $FFE5 ; Make sure we don't continue execution!
clr.w ODsp-hbase(a4)
.odOvfl dc.w $FFE6 ; OD stack overflow
; CmpAddrs compares the A0 and A1 descriptors, and returns with CC "equal" if
; the addresses are the same - defined as the mode, base reg, index reg,
; displacement, size and indirection count all being equal.
CmpAddrs move.b opInd,d0
cmp.b opInd(a1),d0
bne.s .caRtn
move.b opBreg,d0
cmp.b opBreg(a1),d0
bne.s .caRtn
move.b opMode,d0
cmp.b opMode(a1),d0
bne.s .caRtn
cmp.b #mdDn,d0 ; If all equal so far, and mode
beq.s .caRtn ; is Dn or An, everything else is
cmp.b #mdAn,d0 ; irrelevant so we return "equal"
beq.s .caRtn
move.l opDispl,d0
cmp.l opDispl(a1),d0
bne.s .caRtn
move.b opXreg,d0
cmp.b opXreg(a1),d0
bne.s .caRtn
move.b opSize,d0
cmp.b opSize(a1),d0
.caRtn rts
doODvalid
LEA ODsav,A0
LEA OD,A1
BRA MoveDesc
ODvalid macrox
BSR doODvalid
endm
; ChkOpt checks OD to see if any optimization may be possible.
; If there is, it leaves the type field in D1 (lo byte), the subtype
; in D2, and returns with the CC NE. Note that garbage may be
; in the high bytes of D1 and D2. D3 is used, and D0 is not altered.
; If no optimization is possible, ChkOpt returns with the CC EQ, and
; only D1 clobbered.
ChkOpt MOVE.W OD,D1
BEQ.S .retn
; TST.L optq
; BEQ.S .retn
MOVE.L D1,D2 ; Subtype field to D2
LSR.W #8,D1 ; Type field in low byte of D1
MOVEQ #1,D3 ; Set CC non-zero
.retn RTS
; ================================
; LOW LEVEL INSTRUCTION GENERATION
; ================================
; CompMOVEQ compiles a MOVEQ of the number in D1 to the register
; designated by D0. Uses D0.
CompMOVEQ
ANDI.W #$FF,D0
ANDI.W #$FF,D1
ROR.W #7,D0
OR.W D1,D0
ORI.W #$7000,D0
PUSH.L D0
JSR wcomma
RTS
; EAbits ORs in the effective address bits to the opcode in D0, according
; to the descriptor pointed to by A0. Uses D1. A0 is saved.
dc.b $1E,$16,$26
StkCodes
eaModes dc.b $28,$30,$39,$3C,0,$08,$3A,$3B
align
EAbits MOVE.B opMode,D1
EXT.W D1
TST.L opDispl ; If displ field is zero,
BEQ.S .eazd ; check for base-displ mode
.ea1 OR.B eaModes(D1.W),D0
TST.W D1
BMI.S .eaOut
CMP.B #mdAbs,opMode
BEQ.S .eaAbs
cmp.b #mdPC,opMode
beq.s .eaOut
cmp.b #mdPCX,opMode
beq.s .eaOut
.ea2 MOVE.B opReg,D1
AND.B #7,D1
OR.B D1,D0 ; Put in base reg / operand reg
.eaOut RTS
.eazd CMP.B #mdBD,D1 ; If BD mode with zero displ,
BNE.S .ea1 ; we change to reg indirect
OR.W #$10,D0
BRA.S .ea2
.eaAbs MOVE.L opLit,D1
EXG D0,D1
BSR WdChk
EXG D0,D1
SEQ opShort
BNE.S .eaOut
BCLR #0,D0
RTS
; CompLit compiles a literal field. The value is in D0.
compLit PUSH.L D0
CMP.B #Lcode,opSize
BNE.S .clwc
JSR comma
RTS
.clwc JSR wcomma
RTS
; CompExt compiles the extension fields (if any) according to the
; descriptor pointed to by A0. Regs are saved.
CompExt
movem.l d0-d1,-(a6)
cmp.b #mdLit,opMode
beq .exLit
cmp.b #mdAbs,opMode
beq.s .exAbs
cmp.b #mdBD,opMode
beq.s .exBD
cmp.b #mdPC,opMode
beq.s .exPC
cmp.b #mdX,opMode
beq.s .exX
cmp.b #mdPCX,opMode
bne.s .exNone
; Index mode. We assume that the displacement can fit in 8 bits. The caller
; should ensure this, as if the displ is too large, an extra instruction
; (LEA) needs to be generated.
.exX MOVEQ #0,D0
MOVE.B opXreg,D0
bclr #6,d0 ; Set appropriate bit if index is An
beq.s .exX1
bset #3,d0
.exX1 ROR.W #4,D0
OR.W #$0800,D0 ; Note: we assume the index is always long
move.l opDispl,d1
cmp.b #mdPCX,opMode
bne.s .exX2
push.l a1
get.L dp,d1
sub.l opAddr,d1
neg.l d1
pop.l a1
.exX2 or.b d1,d0
BRA.S .ex1
.exBD MOVE.L opDispl,D0
BEQ.S .exNone
.ex1 PUSH.L D0
.ex2 JSR wcomma
.exNone
.exOut movem.l (a6)+,d0-d1
rts
.exAbs PUSH.L opLit
TST.B opShort
BNE.S .ex2
JSR comma
bra.s .exOut
.exLit MOVE.L opLit,D0
bsr CompLit
bra.s .exOut
.exPC push.l a1
get.L dp,d0
sub.l opAddr,d0
neg.l d0
pop.l a1
bra.s .ex1
GetSize
MOVEQ #0,D1
MOVE.B opSize,D1
ROR.B #2,D1
OR.W D1,D0
RTS
GetReg
MOVEQ #0,D1
MOVE.B opToFrom,D1
ROR.W #7,D1
OR.W D1,D0
RTS
; CompMOp compiles an operation with an ea, according to the descriptor
; pointed to by A0. The desired opcode is in D0. If the register field
; is OK already or is irrelevant, enter at CompMOp1. Likewise if both the
; reg and size fields are OK or irrelevant, enter at CompMop2.
; A0 is saved.
CompMOp BSR.S GetReg
CompMop1
BSR.S GetSize
CompMop2
BSR EAbits
CompMOp3
PUSH.L D0
JSR wcomma
BRA.S CompExt
; CompLEA compiles an LEA (what else?). The opind field and flags field are ignored
; - they should already have been looked after.
; D0 = A reg no.
CompLEA
and.w #7,d0
tst.l opDispl
beq.s .clzd
.cl1 move.w #$41C0,D1 ; LEA addr,An
.cl2 ror.w #7,D0
or.w D1,D0
bra.s CompMop2
.clzd cmp.b #mdX,opMode ; If zero displ, maybe we can optimize.
beq.s .clX ; Handle index mode separately
cmp.b #mdBD,opMode
bne.s .cl1
move.b opBreg,d1 ; BD mode. Is dest reg same as base?
and.b #7,d1
cmp.b d0,d1
bne.s .cl1 ; No
rts ; Yes: We don't need to compile anything!
.clX MOVE.B opBreg,D1 ; Index mode with zero displ.
AND.B #7,D1
CMP.B D1,D0 ; Same base and dest reg?
BNE.S .cl1 ; No
MOVE.W #$D1C0,D1 ; Yes. Substitute ADDA Dn,An
MOVE.B #mdDn,opMode
MOVE.B opXreg,opReg
BRA.S .cl2
; CompPOPReg compiles a POP.L Dn/An where n is in D0 on entry.
; Uses D1.
CompPOPReg
MOVE.W D0,D1
AND.W #AnReg,D1
AND.W #7,D0
ROR.W #7,D0
OR.W D1,D0
OR xpopD0,D0
PUSH.L D0
JSR wcomma
RTS
; CompMOVEM compiles a MOVEM.
; Entered with:
; D0 = ea bits (this routine ORs in the right opcode)
; D1 = mask (not reversed yet for predecrement mode)
; D2 (lo byte) = flags:
; bit 0: direction (0 = regs to mem, 1 = mem to regs)
; bit 1: 1 = predecrement 0 = everything else.
;
; A0 -> memory operand descriptor, if an extension needs to be compiled
; (i.e. mode isn't predecrement or postincrement). Otherwise ignored.
; Preserves all D regs.
CompMOVEM
loc
movem.l d0-d3,-(a6) ; Save regs
and.l #$FFFF,d1 ; Mask out any garbage in high word of D1
beq.s .out ; If no regs to be moved, don't compile
; anything
bsr LowBit ; Check if only 1 reg to be moved
blt.s .oneReg
move.l (a6),d0 ; Recover D0
btst #1,d2
bne.s .predec
.cmm1 ; All modes except predecrement.
or.w #$48C0,d0 ; MOVEM op to D0
btst #0,d2 ; Set Direction bit appropriately
beq.s .cmm2
bset #10,d0
.cmm2 swap d0
move.w d1,d0
push.l d0
jsr comma ; Compile the op
.cmmExt btst #1,d2 ; Now check if we need to compile an extension
bne.s .out ; Not if predecrement
move.l (a6),d0 ; Recover ea bits in D0
and.b #$38,d0
cmp.b #$18,d0
beq.s .out ; Not if postincrement either
bsr CompExt ; Otherwise compile ext - we hope A0 is valid!
.out movem.l (a6)+,d0-d3 ; Restore regs
rts ; and out.
.predec ; Predecrement mode. We need to reverse
exg d0,d1 ; the mask
bsr BitReverse
exg d0,d1
bra.s .cmm1 ; Then proceed as above.
.oneReg
move d0,d1 ; Move bit# to D1 (don't need mask now)
move.l (a6),d0 ; Recover D0
btst #0,d2
beq.s .1r2m
cmp.b #7,d1
ble.s .1r1
subq.b #8,d1
or.w #40,d0
.1r1 ror.w #7,d1
.1rCmpl or.w d1,d0
or.w #$2000,d0
push.l d0
jsr wcomma
bra.s .out
.1r2m move d0,d3
and.w #7,d0
lsl #6,d0
and #$38,d3
or d3,d0
lsl #3,d0
cmp.b #7,d1
ble.s .1rCmpl
subq.b #8,d1
or.w #8,d0
bra.s .1rCmpl
; RevCond reverses the selected condition for a branch; i.e. there are two
; operands whose positions are to be reversed. We assume that CMPdesc points
; to the comparison descriptor.
RevCond PUSH.L A0
move.l CMPdesc,a0
CMP.B #7,opSubType ; No action if code = EQ
BEQ.S .getout
CMP.B #6,opSubType ; or NE
BEQ.S .getout
EOR.B #3,RCond-hbase(A4) ; Otherwise we flip the two low bits.
.getout POP.L A0
RTS
; ViaD compiles a MOVE sequence to use Dn or A0 as intermediate storage.
; This is normally to lengthen the operand. We use A0 if we need to
; sign-extend from word to long, since this occurs automatically in the
; A registers.
;
; A0 -> source descriptor
; A1 -> destination descriptor
;
; Both are preserved.
direct byte
byte2L byte
clearD byte
align
ViaD MOVEQ #2,D0
CLR.B direct-hbase(A4)
CLR.B byte2L-hbase(A4)
BTST #flExt,opFlags ; Sign-extend?
BEQ.S .vdD ; No - use D reg
tst.b svInhibitClr-hbase(a4) ; Inhibit clear/extend?
bne.s .vdD ; Yes - use D reg
CMP.B #Wcode,opSize ; No - is it word to long?
BEQ.S .vdA ; Yes - use A reg
ST byte2L-hbase(A4) ; No, byte to long. Remember that.
.vdD sf clearD-hbase(a4) ; Use a D reg.
cmp.b #Lcode,opSize(A0) ; Find out if we need to clear it first.
beq.s .vdd0 ; Don't need to clear reg if src
btst #flExt,opFlags(A0) ; is long, or if we're sign extending
bne.s .vdd0 ; or if we're inhibiting the clear
tst.b svInhibitClr-hbase(a4)
seq clearD-hbase(a4) ; Set flag true if clear needed
.vdd0 CMP.B #mdDn,opMode(A1) ; Is dest already a D reg?
BNE.S .vdd2 ; No
; Dest is already a D reg. We might be able to go directly to this as the
; "temporary", and not move it anywhere at the end.
; But first we need to check for one possible problem case - where we need
; to clear the reg first, but the source is index mode, with the same index
; reg as the dest D reg.
move.b opReg(a1),d1 ; Dest reg no to D1
tst.b clearD-hbase(a4)
beq.s .vdd1 ; If no clear, no problem
cmp.b #mdX,opMode(a0)
bne.s .vdd1 ; If source not index mode, no problem
cmp.b opXreg(a0),d1 ; Regs same?
beq.s .vdd2 ; YES - problem - don't go direct!
.vdd1 move.b d1,d0 ; No problem. Set "temp" reg number
st direct-hbase(A4) ; and set "direct" flag
.vdd2 PUSH.L A1 ; Save dest desc pointer
LEA ODreg,A1 ; And use ODreg as desc for D reg
MOVE.B D0,opReg(A1)
MOVE.B #mdDn,opMode(A1)
tst.b clearD-hbase(a4) ; Clear the reg?
beq.s .vd2 ; No
MOVEQ #0,D1 ; Yes
BSR CompMOVEQ ; Generate MOVEQ to clear it
BRA.S .vd2
.vdA PUSH.L A1 ; Sign extension from word to long.
cmp.b #mdAn,opMode(a1) ; Is dest already An?
seq direct-hbase(a4) ; If so, go directly there
beq.s .vd2
lea ODreg,A1 ; Otherwise we use A0 as the
clr.b opReg(A1) ; intermediate register.
move.b #mdAn,opMode(A1)
.vd2 MOVE.B opSize(A0),opSize(A1)
BSR.S compMove ; Compile the MOVE to Dn/An
TST.B byte2L-hbase(A4) ; Extend from byte to long in Dn?
BEQ.S .vd3 ; Not if we didn't set the flag
tst.b svInhibitClr-hbase(a4)
bne.s .vd3 ; Or if we're inhibiting clear/extend
MOVEQ #0,D0 ; Yes, we'll do it.
MOVE.B opReg(A1),D0 ; Get reg #
MOVE.L D0,D1
OR.W #$4880,D0 ; EXT.W Dn
SWAP D0
MOVE.W D1,D0
OR.W #$48C0,D0 ; EXT.L Dn
PUSH.L D0
JSR comma
.vd3 POP.L D0 ; Recover dest desc pointer
PUSH.L A0 ; Save A0
MOVE.L A1,A0 ; A0 -> intermediate desc ODreg
MOVE.L D0,A1 ; A1 -> original dest desc
TST.B direct-hbase(A4) ; Did we go direct to the dest reg?
BNE.S .vdEnd ; Yes - we're finished.
MOVE.B opSize(A1),opSize ; Otherwise compile MOVE from
BSR.S compMove ; Dn/An to dest
.vdEnd POP.L A0 ; Restore A0
RTS
; =========================
; COMPMOVE
; =========================
; CompMOVE (surprise, surprise) compiles a MOVE.
;
; A0 -> source descriptor
; A1 -> destination descriptor.
; The operand lengths don't have to match - we generate any appropriate
; extra instructions to sort things out.
compMOVE
moveq #0,d0
moveq #0,d1
cmp.b #mdLit,opMode(a0)
beq .mvLit ; If source is literal, handle it
; First we check if it's the address to be moved, not the operand. This
; only makes sense for some modes. For modes mdAbs, mdLit, mdDn, mdAn
; or the stack modes, we IGNORE the opind field.
tst.b opMode(a0)
bmi.s .mvFPck ; Skip check if stack mode
cmp.b #mdX,opMode(a0)
bls.s .mvck
cmp.b #mdPC,opMode(a0)
blo.s .mvFPck ; Or if mdAbs, mdLit, mdDn or mdAn
.mvck TST.B opind ; Now here's the check.
BEQ .mvAddr ; If opind=0, go move the address
.mvFPck btst #flFP,opFlags(a0) ; If either operand is floating,
bne .mvFP ; call FP move routine
btst #flFP,opFlags(a1)
beq.s .mvsz
.mvFP movem.l a0/a1,-(a6)
bsr FPmove
or.b d0,FPdispflg-hbase(a4)
bsr chkFPdisp
movem.l (a6)+,a0/a1
rts
MoveTbl dc.w $1000,$3000,$2000
LengthTable
dc.b 1,2,4
align
.mvsz MOVE.B opSize(A0),D0 ; Normal move.
MOVE.B opSize(A1),D1
CMP.B #Lcode,D1 ; If dst is not long
BEQ.S .mv0 ; and the source is the stack
TST.B opMode(A0) ; then we have to go via a D reg.
BMI ViaD
.mv0 MOVE.B LengthTable(D0.W),D0
MOVE.B LengthTable(D1.W),D1
SUB.B D1,D0
BLT ViaD
BEQ.S .mv2
ADD.L D0,opDispl(A0)
.mv1 MOVE.B opSize(A1),opSize(A0)
.mv2 MOVEQ #0,D0
MOVE.B opSize(A0),D0
ADD.W D0,D0
MOVE.W MoveTbl(D0.W),D0 ; Get right MOVE opcode
BSR EAbits ; OR in source mode/reg
PUSH.L D0 ; Now we get the dest reg/mode Save src
EXG A0,A1
MOVEQ #0,D0
BSR EAbits
MOVE.L D0,D1
AND #7,D0
LSL #6,D0
AND #$38,D1
OR D1,D0
LSL #3,D0 ; Get into right place
OR.L D0,(A6) ; OR with src - final opcode in stk
JSR wcomma
EXG A0,A1
BSR CompExt ; Compile source extension
EXG A0,A1
BSR CompExt ; And destination extension
EXG A0,A1 ; Put A0 and A1 back to what they were
RTS
; Literal source. We may be able to optimize.
.mvLit move.l opLit,d1
move.l d1,d0
bsr ByteChk
bne .mv1
; TST.B opShort ; Short?
; BEQ .mv1 ; No - just compile literal mode src
; MOVE.L opLit,D1 ; Yes. Value to D1
CMP.B #mdDn,opMode(A1) ; Is destination Dn?
BNE.S .mvLm ; No
MOVE.B opReg(A1),D0 ; Yes. Reg no to D0
BRA CompMOVEQ ; compile MOVEQ, return
.mvLm TST.L D1 ; Memory or stack. Is number zero?
BEQ.S mvZero ; Yes
MOVEQ #2,D0 ; No
BSR CompMOVEQ ; Compile MOVEQ #nn,D2
PUSH.L A0 ; Save A0
LEA ODreg,A0 ; Change source to D2
MOVE.B #mdDn,opMode
MOVE.B #2,opReg
BSR .mv1 ; Compile MOVE
POP.L A0 ; Restore A0
RTS
mvZero EXG A0,A1 ; Literal zero. We compile a
MOVE.W xclrD0,D0 ; CLR instead.
BSR CompMOp1
EXG A0,A1
RTS
.mvAddr ; opind field is zero, so we need the
; address, not the operand.
MOVEQ #0,D0
CMP.B #mdAn,opMode(A1)
BEQ.S .mva1
CMP.B #mdBD,opMode
BNE.S .mva0
TST.L opDispl ; If source is BD mode with zero displ,
BEQ.S .mva2 ; we'll change it to An direct
.mva0 BSR CompLEA ; Compile LEA addr,A0
PUSH.L A0 ; Save src desc pointer
LEA ODreg,A0
MOVE.B #mdAn,opMode
CLR.B opReg
MOVE.B #Lcode,opSize
BSR .mvsz ; Compile MOVE A0,dst
POP.L A0
RTS
.mva1 MOVE.B opReg(A1),D0
BRA CompLEA
.mva2 MOVE.B #mdAn,opMode
BRA .mvsz
; =======================
; FPMOVE
; =======================
; FPmove compiles a floating-point move. A0 -> source desc, A1 -> dest desc.
; leaves D0 non-zero if source was from FP heap (as it might need disposing).
; If the destination requires a new heap location we compile the call to
; create it straight away (since we need it right now!).
; First, some utility routines:
loc
.FPreg moveq #0,d1
move.b opReg(a1),d1
; move.b #$80,d2
; lsr.b d1,d2
; or.b d2,d0
lsl.w #7,d1
or.w d1,d0
rts
ToNewHeap
bsr CompFPnew
push.l a1
exg a0,a1
bsr newClrOD
move.b #mdBD,opMode
move.b #AnReg,opBreg
move.b #1,FPA-hbase(a4)
clr.l opDispl
move.b #1,opind
move.b #fbFP,opFlags
exg a0,a1
bsr FPmove1
exg a0,a1
pop.l a1
move.b #mdAn,opMode
clr.b opFlags
bsr compMove
bra releaseOD
UsePrevFPlit byte
align
CompFLit
movem.l a0/a1,-(a6)
get.l ptrFPULit,-(a6)
bsr CompJSRnoPush
tst.b usePrevFPlit-hbase(a4)
bne.s .cflPrev
lea svFPlit,a0
.cfl1 push.l (a0)+
jsr comma
push.l (a0)+
jsr comma
push.l (a0)+
jsr comma
movem.l (a6)+,a0/a1
clr.b FPA-hbase(a4) ; Destination operand must use A0 since
rts ; literal will be addressed via A1
.cflPrev sf usePrevFPlit-hbase(a4)
lea prevFPlit,a0
bra.s .cfl1
FspecChk
btst #flLit,opFlags ; Is operand a floating literal?
beq.s .fsChkCR
bsr CompFLit ; Yes. Compile literal sequence.
bra.s .fsOut
.fsChkCR
btst #flFCR,opFlags ; Is it a constant ROM reference?
beq.s .fsOut ; No
move.l #$F2005C00,d0 ; fmoveCR #0,FP0
or.b opRoffs,d0 ; OR in right CROM offset
push.l d0
jsr comma
.fsOut rts
FPmove
bsr.s FspecChk ; If source special, compile approp sequence
FPmove1 movem.l d5-d7/a0/a1,-(a6)
moveq #0,d7
btst #flLit,opFlags
sne d5 ; If it was literal, flag this in D5
cmp.b #mdFPn,opMode(a0)
beq .op1reg
cmp.b #mdFPn,opMode(a1)
beq .memToReg
; Neither is FPn.
btst #flFP,opFlags(a1)
bne.s .fpm0
bsr.s ToNewHeap
bra .fpmOut
.fpm0 move.b FPA,d0
btst #flFP,opFlags ; Is src floating data?
bne.s .op1fv
moveq #1,d7
bsr FetchToA ; No. Get source addr to reqd A reg
eor.b #1,FPA-hbase(a4) ; And use the "other" reg next time
bra.s .fpm1
.op1fv tst.b d5 ; Yes. Is source Literal?
bne.s .fpmLit
bsr compLEA ; No. Get source addr to reqd A reg.
; Note: can't use FetchToA here as FP flag bit is set which can cause
; disasters. But we don't need to anyway.
eor.b #1,FPA-hbase(a4) ; Use "other" reg next time
bra.s .fpm1
.fpmLit clr.b FPA-hbase(a4) ; Literal source. This uses A1 for the
; access, so dest addressing must use A0
; no matter what.
.fpm1 move.b FPA,d0
exg a0,a1
btst #flFP,opFlags ; Is dest floating data?
bne.s .op2fv
bsr FetchToA ; No. Get dest addr to reqd A reg
bra.s .fpm2
.op2fv bsr compLEA ; Yes. LEA dest addr to reqd A reg
.fpm2 exg a0,a1
tst.b FPA-hbase(a4) ; If src uses A1, we use the
beq.s .fpmA1A0 ; other move sequence (A1 src, A0 dest).
tst.b d5
bne.s .fpmA1A0
compopl xFPmove ; movem.l (a0),d0-d2
compopl xFPmove+4 ; movem.l d0-d2,(a1)
bra.s .fpmOut
.fpmA1A0 compopl xFPmove2 ; movem.l (a1),d0-d2
compopl xFPmove2+4 ; movem.l d0-d2,(a0)
.fpmOut move.l d7,d0 ; Return "heap to dispose" flag in D0
and.b #fbLit,d5 ; Restore Literal flag in src descriptor
or.b d5,opFlags
movem.l (a6)+,d5-d7/a0/a1
rts
.op1reg cmp.b #mdFPn,opMode(a1)
bne.s .regToMem
moveq #0,d0
; Move reg to reg.
move.b opReg,d0
cmp.b opReg(a1),d0
beq.s .fpmOut ; If the same one, get out without compiling
; anything
lsl.w #3,d0
or.b opReg(a1),d0
lsl.w #7,d0
swap d0
move.w #$F200,d0 ; Compile: fmove.x FPn,FPm
swap d0
push.l d0
jsr comma
bra.s .fpmOut
.regToMem
btst #flFP,opFlags(a1)
bne.s .regToFv
bsr ToNewHeap
bra .fpmOut
.regToFv ; Compile fmove.x FPn,<ea>
exg a0,a1
move.w #$F200,d0
bsr EAbits
swap d0
move.w #$6800,d0
bsr.s .FPreg
push.l d0
bsr comma
bsr CompExt
exg a0,a1
bra.s .fpmOut
.memToReg
btst #flFP,opFlags(a0) ; Is src a floating value/constant/literal?
bne.s .FVtoReg ; Yes
moveq #1,d7 ; No - there will be heap to dispose
move.b FPA,d0
or.b #1,FPA-hbase(a4)
move.b d0,d6
and.b #7,d6
bsr FetchToA ; move.l <ea>,An
move.w #$F210,d0 ; fmove.x (An),FPn
or.b d6,d0
swap d0
move.w #$4800,d0
bsr.s .FPreg
push.l d0
bsr comma
bra.s .fpmOut
.FVtoReg
; Compile fmovem <ea>,FPn
.fv2r0 move.w #$F200,d0
bsr EAbits
.fv2r1 swap d0
move.w #$4800,d0
bsr.s .FPreg
push.l d0
bsr comma
bsr CompExt
bra.s .fpmOut
;.fLit2r bsr CompFLit
; bsr newClrOD
; addq #1,d5
; move.b #AnReg+1,opBreg
; bra.s .fv2r0
; ===================
; CompMoveToFPn compiles a move from some <ea> given by the A0 desc, to
; FPn where n is in D0.
CompMoveToFPn
movem.l d0/a0/a1,-(a6)
bsr newOD
moveq #0,d0
bsr LoadBase
exg a0,a1
bsr newClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
pop.l d0
move.b d0,opReg
exg a0,a1
bsr FPmove
bsr releaseOD
bsr releaseOD
movem.l (a6)+,a0/a1
rts
; ===================
; CompPopFPn compiles a pop from the stack to FPn, where n is in D0.
; Note that what is actually in the stack is a pointer to the FP heap.
; We assume the caller will handle the disposing of the heap, since it
; may be better to put this after any compiled FP ops, to allow overlap.
CompPopFPn
movem.l a0/a1,-(a6)
bsr newClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b d0,opReg
move.l a0,a1
bsr newClrOD
move.b #stkPop,opMode
move.b #1,opind
bsr FPmove
bsr releaseOD
bsr releaseOD
movem.l (a6)+,a0/a1
rts
; =======================
; LOADBASE
; =======================
; Loadbase is called before we compile any op referencing memory.
; A0 must be pointing to the operand descriptor, and D0 indicates
; which A reg should be used as a temporary for the operand address,
; if necessary.
; Loadbase compiles any necessary preliminary ops to ensure the data is
; properly addressible, and modifies the descriptor appropriately. It's
; the caller's job to create a temp descriptor for this purpose, if the
; original descriptor isn't to be clobbered.
; First we have some utility routines needed by LoadBase.
StoreFlg byte ; Set true if this is a store op, so we
; don't generate a PC-relative store (illegal
; on 68000).
VirtBase byte
LBsavFlgs byte ; Saves flags byte (we clear FP bit and need to restore it)
align
SetupOD
move.b #Lcode,opSize(a1)
move.b #1,opind(a1)
clr.l opDispl(a1)
clr.b opFlags(a1)
move.b opToFrom,D0
bmi.s .toStk
btst #6,d0
bne.s .An
btst #5,d0
bne.s .FPn
move.b #mdDn,opMode(a1)
bra.s .suRtn
.An move.b #mdAn,opMode(a1)
bra.s .and
.FPn move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags(a1)
.and and.b #7,d0
.suRtn move.b d0,opReg(A1)
rts
.toStk move.b d0,opMode(a1)
rts
loc
.LEAfirst
move.b WhichA,d0
bsr CompLEA
move.b WhichA,d0
rts
; We call SetOpAddr to make sure the opAddr field of the A0 descriptor is
; set up. It usually will be, but there are a few situations when it won't.
; If the mode isn't base-displacement, indexed or PC-rel, or if the base reg
; isn't a3, a4 or a5, we put -1 in opAddr, which will maybe cause a trap if it
; gets used.
SetOpAddr
movem.l d0/d1/a1,-(a6) ; Save regs (what else?)
cmp.b #mdBD,opMode
beq.s .ckBreg
cmp.b #mdX,opMode
beq.s .ckBreg
cmp.b #mdPC,opMode
beq.s .soaOut
cmp.b #mdPCX,opMode
beq.s .soaOut
bra.s .noAddr
.ckBreg move.b opBreg,d0
and.b #7,d0
cmp.b #3,d0
beq.s .useLB
cmp.b #4,d0
beq.s .useHB
cmp.b #5,d0
beq.s .useMB
.noAddr moveq #-1,d1
bra.s .soaAdr
.useLB move.l a3,d1
bra.s .soaAdd
.useHB move.l savedA4,d1
bra.s .soaAdd
.useMB get.L MBcomp,d1
.soaAdd add.l opDispl,d1
.soaAdr move.l d1,opAddr
.soaOut movem.l (a6)+,d0/d1/a1
rts
; ======================
LoadBase
or.b #AnReg,D0
move.b D0,WhichA-hbase(A4)
move.b opFlags,LBsavFlgs-hbase(a4)
bclr #flFP,opFlags ; We don't handle floating operands here,
; so we clear the FP bit so as not to
; confuse CompMove.
push.l A1 ; Save A1
tst.b opMode ; If addressed locn is stack,
bpl.s .lbsoa ; we force opSize to Lcode (which it has
cmp.b #1,opind ; to be anyway)
bgt.s .lbsoa
move.b #Lcode,opSize
.lbsoa bsr.s setOpAddr
cmp.b #mdX,opMode
bhi .lb1
; beq .lbX ; BD or index mode.
tst.b opBreg ; Is base reg stack?
bpl.s .lb0 ; No
moveq #0,d0 ; Yes. Compile pop to An
move.b WhichA,d0
and.b #7,d0
ror.w #7,d0
or.w xpopA0,d0
push.l d0
jsr wcomma
move.b WhichA,opBreg ; and change base reg to An in desc.
bra.s .lbReal
.lb0 btst.b #6,opBreg ; Is base reg Dn?
bne.s .lbReal ; No (must be An already)
moveq #0,d0 ; Yes. Compile move to An
move.b WhichA,d0
and.b #7,d0
ror.w #7,d0
or.b opBreg,d0
or.w xMvD0A0,d0
push.l d0
jsr wcomma
move.b WhichA,opBreg ; and change base reg to An in desc.
.lbReal move.l opDispl,d0 ; Base reg is now An. We now need to make
move.b opBreg,d1 ; sure the base/displ in desc is "real".
bclr #6,d1
move.b d1,VirtBase-hbase(a4)
move.l opAddr,d2
bsr GetRealBase
bne.s .lbFar ; If displ won't fit in 16 bits, special
; treatment
cmp.b #mdX,opMode
bne.s .lbOK
bsr ByteChk
bne.s .lbFar ; Or for index mode, it's 8 bits.
.lbOK move.l d0,opDispl ; Adjust descriptor: set real displacement
bset #6,d1 ; Final An number in d1. Set AnReg bit for
; desc
move.b d1,opBreg ; The displ is in 16-bit range from base
bra .lb1
.lbFar ; The displ is too big for a single
; instruction.
move.l d0,opDispl ; Adjust descriptor: set real displacement
bset #6,d1 ; Final An number in d1. Set AnReg bit for desc
move.b d1,opBreg ; Set base reg field in descriptor
; Now we work out how to handle this:
move.l opAddr,d0 ; First we check if PC-rel will work
bmi .lbLEA ; If real addr not available, we'll LEA
get.l dp,d0
bsr getbase
cmp.b virtBase-hbase(a4),d1
bne .lbLEA ; If addr and here are in different dic
; segments, we'll LEA
.lbfPC move.l opAddr,d0
get.l dp,d1
sub.l d1,d0 ; Get PC offset
move.l d0,d1 ; Save in D1
bmi.s .lbf1
addq.l #8,d0 ; Safety margin since dp won't be exactly
bra.s .lbf2 ; right - so we err on the safe side.
.lbf1 subq.l #4,d0
.lbf2 bsr WdChk
bne.s .PCtoofar ; If we're out of 16-bit range
cmp.b #mdX,opMode
bne.s .lbf3
bsr ByteChk ; Or if mode is index, it's 8-bit range
beq.s .lbfOK
.PCtoofar ; Too far for straight PC-rel instruction.
cmp.b #noReg,opBreg ; If no base reg available, must use PC-rel
; anyway
bne.s .lbLEA ; Otherwise we'll LEA.
push.l d1
move.w #$203C,d0 ; Compile MOVE.L #<displ>,D0
push.l d0
jsr wcomma
jsr comma
clr.l opDispl ; Displ is now zero, and index reg is D0.
clr.b opXreg
get.l dp,d0
subq #6,d0
move.l d0,opAddr
; Reset opAddr so CompExt will compile the
; right displacement. We have already factored in the
; distance between the desired addr and DP, so
; now we just have to allow for the 6 bytes we
; just compiled.
.lbfOK move.b #mdPCX,opMode ; OK, we're in range. Set PC with index mode
bra.s .lbf4
.lbf3 move.b #mdPC,opMode ; Set PC plus displ mode
.lbf4 get.B fmkCnt,D2 ; For both PC modes, we set CallOut if reqd
put.B D2,callOut ; so this code won't be moved (which would
; invalidate the PC offset!)
tst.b StoreFlg-hbase(a4)
beq .lb1 ; If not storing, addr is OK now.
bsr .LEAfirst ; Storing: compile LEA of addr
move.b #mdBD,opMode ; Mode now becomes BD
bset #6,d0
move.b d0,opBreg
clr.l opDispl ; With zero displ
bra.s .lb1
.lbLEA ; We need to generate an LEA for the addr.
move.l opDispl,d0
bsr WdChk ; In 16-bit range from base reg?
bne.s .lblAdd ; No
move.b opMode,d0 ; Yes. Save mode
push.l d0
move.b #mdBD,opMode ; Set BD mode
bsr .LEAfirst ; Compile LEA
bset #6,d0
move.b d0,opBreg ; Put new base reg in descriptor
pop.l d0 ; Restore mode
move.b d0,opMode
clr.l opDispl ; Displ now is zero
bra.s .lb1
.lblAdd moveq #0,d0 ; Out of 16-bit range.
move.b WhichA,d0
and.b #7,d0
ror.w #7,d0
push.l d0
or.b opBreg,d0 ; (Note: doesn't matter that AnReg bit is
; set)
or.w #$2048,d0 ; Compile MOVEA.L A<base>,An
push.l d0
jsr wcomma
pop.l d0
or.w #$D1FC,d0
push.l d0
jsr wcomma ; ADDA.L #displ,An
push.l opDispl
jsr comma
move.b WhichA,opBreg ; Reset base reg to An in descriptor
clr.l opDispl ; Displ now is zero
.lb1 cmp.b #1,opind
ble.s .lbRtn
cmp.b #mdAn,opMode ; Indirect count is more than 1
bne.s .nOD ; Is mode An direct?
move.b #mdBD,opMode ; Yes - change to base-displ (zero displ),
subq.b #1,opInd ; reduce indirect cnt by 1
bra.s .lb1 ; and try again.
.nOD bsr newOD ; No - first we compile a load of the source
move.b #Lcode,opSize ; to an A reg.
move.b WhichA,opToFrom
bsr CompFetch1
clr.l opDispl
move.b #mdBD,opMode ; Mode must now be BD with zero displ
move.b WhichA,opBreg ; (which eventually compiles as An indirect)
moveq #0,d0
move.b opInd,D0
subq #3,D0
bmi.s .lbRel
.lbLoop push.l d0
bsr.s CompFetch1
pop.l d0
dbra d0,.lbLoop
.lbRel bsr ReleaseOD ; Now update passed-in desc
clr.l opDispl
move.b #mdBD,opMode ; May have been index mode
move.b WhichA,opBreg
move.b #1,opInd
.lbRtn pop.l A1 ; Restore A1
btst #flFP,LBsavFlgs ; And FP bit in flags
beq.s .lbOut
bset #flFP,opFlags
.lbOut rts
; =======================
; COMPFETCH and COMPSTORE
; =======================
; CompFetch and CompStore and are higher-level than compMOVE, and are
; called when we need to compile instructions to move data between memory
; and the stack or a D register. We allow a few abstractions such the stack
; being an index register, lobase always addressing the main dic and
; 4-byte displacements.
; Here we only need the A0 descriptor, since the "other" location (reg or stack)
; participating in the operation is marked in the opToFrom field.
; A0 and A1 are preserved.
; Both CompFetch and CompStore allocate a temporary descriptor, then call
; LoadBase to handle the above abstractions. LoadBase generates any
; necessary extra instructions, and modifies the temp descriptor
; appropriately. LoadBase gets called from a lot of other places as well
; - note it modifies whatever descriptor is passed to it, so a temp copy
; should be used if the original is still needed.
CompFetch1
MOVE.B FetchSize,D2 ; Pick up requested fetch size
MOVE.B #Lcode,FetchSize-hbase(A4)
; and immediately reset default for safety
LEA ODdst,A1 ; Set up dest desc in ODdst
BSR.S setupOD ; with A1 pointing there
BMI CompMove ; If dest is stack, compile MOVE
MOVE.B D2,opSize(A1) ; It isn't. Set required size.
MOVE.B opMode,D0
CMP.B opMode(A1),D0 ; Are src and dst both Dn or An?
BNE CompMove ; No - compile MOVE
CMP.B #mdDn,D0
BEQ.S .RtoR
CMP.B #mdAn,D0
BNE CompMove
.RtoR MOVE.B opReg,D0
CMP.B opReg(A1),D0 ; Yes. Same register?
BEQ.S .RtoR1 ; Yes - don't compl anything, no matter
; what.
TST.B svForceToR-hbase(A4)
; No. Are we forcing a reg to reg move?
BNE CompMOVE ; Yes - compile MOVE
.RtoR1 MOVE.B opReg,D3 ; No - don't compile anything - and set up
BSR ReleaseOD ; to return from CompFetch, not CompFetch1.
; Careful - this is a bit sneaky.
; Release temp OD,
MOVEM.L (A6)+,A0/A1 ; restore A0, A1
MOVE.B D3,opToFrom ; Set opToFrom to the reg actually used
ADDQ #4,A7 ; Pop rtn addr from CompFetch
RTS ; Return to original caller
CompFetch
movem.l a0/a1,-(a6) ; Save regs
move.b ForceToR,svForceToR-hbase(a4)
sf ForceToR-hbase(a4)
move.b InhibitClr,svInhibitClr-hbase(a4)
sf InhibitClr-hbase(a4)
BSR NewOD ; A0 -> temp OD
; Now we check for the case where the source is FP and the dest is the stack.
; In this case a floating heap location will be allocated for the dest by
; FPmove, using A0, so we'll need to use A1 for the LoadBase. In all other cases
; we use A0 for the LoadBase.
btst #flFP,opFlags
beq.s .cf1
tst.b opToFrom
bpl.s .cf1
moveq #1,d0
bra.s .cf2
.cf1 moveq #0,D0
.cf2 bsr LoadBase
bsr.s CompFetch1
bsr ReleaseOD ; Finished: release temp OD,
movem.l (A6)+,A0/A1 ; restore regs then return.
rts
destFP byte
align
CompStore
MOVEM.L A0/A1,-(A6) ; Save A0, A1
st StoreFlg-hbase(a4)
; move.b opFlags,destFP-hbase(a4) ; Remember if dest is floating
BSR NewOD ; A0 -> temp OD
MOVEQ #1,D0
BSR LoadBase
cmp.b #otFPmon,(a0)
blt.s .cs1
cmp.b #otFPend,(a0)
blt.s .csFPmon
.cs1 lea ODsrc,A1 ; Set up source desc in ODsrc
bsr.s setupOD ; with A1 pointing there
exg A0,A1 ; Exg A0 and A1 so src and dest are right
; btst #flFP,destFP-hbase(a4)
; beq.s .cs2
; bset #flFP,opFlags(a1) ; Set dest floating flag if nec
bra.s .cs2
.csFPmon ; "Store" is an FP monadic op on dest
; location
move.l a0,a1 ; src and dst descriptors are the same
bra.s .cs2
CompStore1 ; Enter here if the two descriptors are set up already
movem.l a0/a1,-(a6) ; Save A0, A1
bsr NewOD ; A0 -> temp OD
.cs2 move.b (a1),operation-hbase(a4)
move.b opShiftCnt,shiftCnt-hbase(a4)
clr.b FPA-hbase(a4) ; In case it's an FP op
bsr OP2
bsr ReleaseOD
sf StoreFlg-hbase(a4) ; Restore default to StoreFlg (false)
movem.l (a6)+,a0/a1 ; restore A0, A1 then return.
rts
; CompAnyNew is higher-level than CompFetch and CompStore. It compiles an
; arbitrary descriptor, which must be in ODnew. We assume saveOD has
; previously been called, we optimize if possible, and in the case
; of a fetch, push the descriptor at the end.
CompAnyNew
LEA ODnew,A0
CMP.B #otJSR,(A0)
BEQ.S .doJSR
CMP.B #otFetch,(A0)
BLT.S .doStore
MOVE.B #stkPush,opToFrom
BSR CompFetch
BRA pushOD
.doStore
MOVE.B #stkPop,opToFrom
CMP.B #otStore,(A0)
BEQ stchk ; Maybe optimize if this is a straight store
BRA CompStore
.doJSR MOVEM.L A0/A1,-(A6) ; Save A0, A1
BSR NewOD ; A0 -> temp OD
MOVEQ #0,D0
BSR LoadBase
MOVE.W #$4E80,D0 ; JSR opcode
BSR CompMop2
BSR ReleaseOD ; Finished: release temp OD,
MOVEM.L (A6)+,A0/A1 ; restore A0, A1 then return.
RTS
; =================================
; FetchToA and FetchToD compile the A0 descriptor (which we assume is a fetch
; descriptor) so that its destination is an A or D register respectively.
; This will be the reg whose number is in D0, unless we are doing FetchToD
; and the fetch's source was already a D register. In that case
; nothing is compiled, but the descriptor's ToFrom field is set to
; that register, so we know that that is where the data is.
; The A/D reg number actually used is left in D0. It may not be the same
; as that requested.
FetchToA
OR.B #AnReg,D0
FetchToD
FetchToReg
MOVE.B D0,opToFrom ; Set dest to Dn/An
BSR CompFetch ; Recompile - dest reg may change
MOVEQ #0,D0
MOVE.B opToFrom,D0 ; Leave reg no in D0
AND.B #7,D0
RTS
; GetToReg is a bit higher-level than FetchToD etc. It gets the top operand
; to the requested register.
; It will work in any situation, and checks the preceding descriptors to see
; if they can be recompiled to get the operand to the requested register.
; D0 on entry is as for FetchToReg.
; D0 on exit is left indicating the actual reg used.
; We do guarantee that if a D reg is requested, a D reg will be used, and
; likewise for an A reg. In actual fact, if A0 is requested it will always
; be used (since any arithmetic on an A reg will always be on A0)
; - hStkObj assumes this!!!
GetToReg
movem.l a0/a1,-(a6)
bsr ChkOpt
beq.s .gdNo
cmp.b #otFetch,D1
beq.s .gdF
cmp.b #otPMend,d1
bge.s .gdNo
; Preceding op is integer arith/logical.
move.b d0,d2
and.b #AnReg,d2
beq.s .g2rOP ; If we're requesting An
cmp.b #otADD,D1 ; we can't optimize unless the
blt.s .gdNo ; op is ADD or SUB, since we can
cmp.b #otSUB,D1 ; use An as a work reg.
bgt.s .gdNo
.g2rOP PUSH.L D0
LEA ODsav,A0
BSR op2Reg
MarkDP
MOVE.L (A6),D1
MOVE.B D1,D2
EOR.B D0,D2
AND.B #AnReg,D2
BEQ.S .gdOut ; Out if we wanted a D (A) and got a D (A)
MOVE.W D1,D3
AND.W #AnReg,D3 ; AnReg bit is set in D3 if dest to be An
SEQ D2
AND.W #8,D2 ; $8 bit is set in D2 if src to be An
AND.W #7,D0
AND.W #7,D1
ROR.W #7,D1
OR.W D1,D0
OR.W D2,D0
OR.W D3,D0
OR.W #$2000,D0 ; MOVE.L srcReg,dstReg
PUSH.L D0
JSR wcomma
POP.L D0
bra.s .gdRtn
.gdOut ADDQ #4,A6
.gdRtn movem.l (a6)+,a0/a1
rts
.gdF LEA ODsav,A0 ; Fetch preceded.
backDP
BSR.S FetchToReg
bra.s .gdRtn
.gdNo MOVE.W D0,D1 ; No optimization possible.
AND.W #AnReg,D1
AND.W #7,D0
PUSH.L D0
ROR #7,D0
OR.W xPopD0,D0
OR.W D1,D0
PUSH.L D0
JSR wcomma ; Compile POP.L <reg>
POP.L D0
bra.s .gdRtn
; ===================
; OP2
; ===================
; OP2 compiles all dyadic operations that can be optimized.
; The kind of operation is indicated in Operation (byte) on entry.
;
; A0 -> source descriptor
; A1 -> destination descriptor
;
; Note that any necessary calls to LoadBase must have been done already, since
; here we don't always know which A regs are available.
; So here we DON'T check the opind field, except to check if it's zero.
;
; For monadic operations, A0 is ignored.
loc
Operation byte ; Saves operation code
ShiftCnt byte ; Shift count for shifts
align
RevOpnds dc.w 0
LitVal long ; Saves any literal value
OP2 moveq #0,d2
move.b Operation,d2 ; Operation code to D2
cmp.b #otStore,d2
beq compMOVE ; If MOVE
cmp.b #otMon,d2
blt.s .opSR
cmp.b #otSHIFT,d2
ble .opMon ; If monadic (NEG, NOT or const shift)
.opSR MOVEM.L A0-A2,-(A6) ; Save regs
cmp.b #otFPops-1,d2
bge .opFP ; If floating-point (including FCMP)
CMP.B #mdLit,opMode(A1)
BEQ .opLitC ; If 2nd operand is literal, must be CMP
CMP.B #mdAn,opMode(A1)
BEQ.S .opDstAn ; If dest An, some things are different
CMP.B #mdLit,opMode
BEQ .opLit ; If 1st opnd is literal
; Now we've got rid of the special cases, either the source or destination
; (or both) must be Dn. If EOR, the source must be Dn.
.opToD
cmp.b #mdDn,opMode(A1) ; If dest isn't Dn, force source to Dn
bne .srcToD
cmp.b #otEOR,D2 ; .. ditto if operation is EOR
beq .srcToD
tst.b opind ; Dest is Dn
beq.s .opSrcAd ; Source is an addr. Get it to A0
cmp.b #Lcode,opSize(a0) ; Otherwise if it's a long op, we can
beq.s .opComp ; compile it straight away
cmp.b #Wcode,opSize(a0)
bne .srcToD ; If byte op, force source to Dn
btst #flExt,opFlags(a0)
beq .srcToD ; Likewise if word but no extension
.opSrcAd moveq #0,d0 ; Word with extension. Get source
bsr FetchToA ; to A0 to extend it
UseODsrc
move.b #mdAn,opMode
move.b d0,opReg
moveq #0,d2
move.b operation,d2
cmp.b #otSUB,d2 ; Is op add or subtract?
bgt .srcToD ; No - can't use A0 as source, so
; move it to Dn
.opComp move.b opReg(A1),opToFrom(A0) ; If we got here, we can compile the
lsl.w #1,d2 ; operation straight away.
lea xadds-(otADD*2),a2
move.w 0(a2,d2.w),d0
.op3 bsr CompMOp
bra .opRtn ; Finished.
.opDstAn ; Destination is An.
cmp.b #otSUB,d2
bgt .srcToD ; If not ADD or SUB, we'll make the
; src Dn and deal with it a bit later
cmp.b #mdLit,opMode ; Check for a literal source
bne.s .opa1 ; It isn't
move.l opLit,d0 ; It is. We can't use ADDI/SUBI here,
moveq #-8,d1 ; only ADDA/SUBA or ADDQ/SUBQ.
cmp.l d1,d0
blt.s .opa1 ; Can we use ADDQ/SUBQ?
moveq #8,d1
cmp.l d1,d0
ble .opQ ; Yes: do it
.opa1 MOVE.B opReg(A1),opToFrom(A0) ; In all other cases we must use
lsl.w #1,d2 ; ADDA/SUBA.
lea xadds-(otADD*2),a2
move.w 0(a2,d2.w),d0
OR.W #$1C0,D0 ; Make the op ADDA.L or SUBA.L
BRA.S .op3
; Monadic operation. A1 desc is the only operand.
.opMon move.l a1,a0
cmp.b #otSHIFT,d2
beq.s .opShift
lsl.w #1,d2
lea xadds-(otADD*2),a2
move.w 0(a2,d2.w),d0
bra CompMOp1
; Immediate shifts. These are like monadic ops, except that the operand being
; shifted must be in Dn, unless it's a shift of one. So if it isn't, we have
; to move it to Dn then move it back. We assume the caller has ensured the
; shift count is 8 or less, since that's all we can do in an immediate shift.
.opShift cmp.b #mdDn,opMode
bne.s .opshMem
.opsh1 move.w #$E180,d0 ; LSL/R opcode
or.b opReg,d0
move.b shiftCnt,d1
beq.s .opshOut ; Shift of zero compiles nothing
move.b d1,d2
subq.b #1,d2
beq.s .opshOneL ; If left shift of one (see below)
bpl.s .opsh2
neg.b d1 ; If shifting right, make cnt positive
eor.w #$100,d0 ; and adjust opcode
.opsh2 and.w #7,d1
.opsh3 ror.w #7,d1
or.w d1,d0
.opsh4 push.l d0
jmp wcomma
.opshOneL move.w #$D080,d0 ; Left shift of one:
moveq #0,d1 ; We compile ADD.L Dn,Dn
move.b opReg,d1 ; since this is faster on some CPUs
or.b d1,d0
bra.s .opsh3
.opshMem tst.b opMode ; If not already Dn, should be stack
bpl.s .opshErr ; Deliberate crash if not!
compop xpopD0
clr.b opReg
bsr.s .opsh1
compop xpushD0
.opshOut rts
.opshErr dc.w $FFE8
; Dest wasn't Dn. We must ensure source is.
.srcToD moveq #0,d0
cmp.b #mdDn,opMode
bne.s .opF2D ; Skip FetchToD call if already in Dn
move.b opReg,d0 ; - FetchToD would have recognized this
bra.s .opinD ; and done nothing, but skipping the call
; saves some time.
.opF2D move.b opSize(A1),fetchSize-hbase(A4)
bsr FetchToD ; Forces src to Dn
.opinD MOVE.B #Lcode,fetchSize-hbase(A4)
; Reset default size - shouldn't be necessary
; here, but I'm a suspicious character.
EXG A0,A1 ; Now we look at the dest mode:
MOVE.B D0,opToFrom
.op0 moveq #0,d2
move.b operation,d2
lsl.w #1,d2
lea xadds-(otADD*2),a2
move.w 0(a2,d2.w),d0
cmp.w #otCMP*2,d2
bne.s .opDDchk
; CMP with source Dn. CMP is implicitly "dst Dn" mode, but without the $100 bit set.
; Thus the operands are the "wrong" way around, and we need to call RevCond.
bsr RevCond
bra.s .op2
.opDDchk cmp.b #mdDn,opMode
beq.s .opDD ; If src and dest are both Dn
cmp.b #mdAn,opMode
beq.s .opDA ; If dest is An, something's wrong!
or.w #$100,D0
.op1 move.w RevOpnds,D1
eor.w D1,D0
.op2 bsr CompMOp
.opRtn
CLR.W RevOpnds-hbase(a4)
.opRtn1 MOVEM.L (A6)+,A0-A2
RTS
.opDD ; Src and dst are both Dn. If the op isn't
; EOR, we must use "dst Dn" mode.
CMP.B #otEOR,operation-hbase(a4)
BEQ.S .op1 ; If EOR, go ahead and compile the op
MOVE.B opToFrom,D0 ; Otherwise we have to swap the reg numbers
MOVE.B opReg,opToFrom ; and not set the $100 op-mode bit.
MOVE.B D0,opReg
BRA.S .op1
.opDA dc.w $FFE1 ; We won't ever get here. Never ever.
.opLit TST.W RevOpnds-hbase(A4)
BEQ.S .opL0
MOVEQ #0,D0
BSR FetchToD
BRA.S .opinD
.opL0 move.l opLit,d0
move.l d0,LitVal-hbase(A4)
cmp.b #otSUB,D2
BGT.S .opL1 ; If not plus or minus, we don't
MOVEQ #-8,D1 ; have a "quick" instruction
CMP.L D1,D0
BLT.S .opL1
MOVEQ #8,D1
CMP.L D1,D0
BGT.S .opL1
.opQ TST.L D0 ; We come here to compile a "quick" instrn
BEQ.S .opRtn ; Add or subtract literal zero means do
; nothing
BPL.S .opQ1 ; Compile the "quick" instruction:
NEG.W D0 ; If negative, make positive and change the
EOR.W #3,D2 ; operation (+ or -) appropriately
.opQ1 ROR.W #7,D0
lsl.w #1,d2
lea xaddq-(otADD*2),a2
or.w 0(a2,d2.w),d0
move.l a1,a0
bsr CompMOp1
bra .opRtn
.opL1 bsr ByteChk ; Literal, not quick. Value in D0. Short?
beq.s .srcToD ; Yes - use MOVEQ to Dn. It's shorter and
; faster than immediate mode.
bra.s .opL3
.opLitC bsr RevCond ; We come here for 2nd operand literal
; (must be CMP)
cmp.b #mdLit,opMode
bne.s .opL2
bsr.s CCmp ; If both operands are literal, we have
bra.s .opRtn ; conditional compilation
.opL2 EXG A0,A1
move.l opLit,d0
move.l d0,LitVal-hbase(A4)
bsr ByteChk
beq .srcToD
.opL3 lsl.w #1,d2
lea xaddi-(otADD*2),a2
move.w 0(a2,d2.w),d0
exg a0,a1
bsr GetSize
bsr EAbits
push.l d0
jsr wcomma
move.l LitVal,D0
bsr CompLit
bsr CompExt
bra.s .opRtn
; Compare with both operands literal. We take this as conditional
; compilation.
CCmp PUSH.L A2 ; Save A2
PUSH.L D0 ; Save D0
MOVE.B condition,D2 ; Get condition
MOVE.B Rcond,D1 ; And reversed condition flag
OR.B #$50,D2 ; Form Scc opcode byte
EOR.B D1,D2
CLR.B Rcond-hbase(A4)
MOVE.B D2,.doit-hbase(A4) ; Store Scc for execution
BSR FlushCache
POP.L D0 ; Is this a test or compare?
BEQ.S .cc1
MOVE.L opLit,D0
CMP.L opLit(A1),D0 ; Compare
BRA.S .doit
.cc1 TST.L opLit ; Test
.doit SEQ D0 ; Scc patched here!
TST.B ifFlg-hbase(A4)
bne.s .setCCmpFlg
MOVE.B D0,D1
MOVEQ #0,D0
BSR CompMOVEQ
MOVE.B #6,condition-hbase(A4)
BRA.S .ccOut
.setCCmpFlg
ADDQ.B #2,D0
PUSH.L A1
put.b D0,CCmpFlg
POP.L A1
.ccOut POP.L A2 ; Restore A1 and A2
RTS
; Floating point operations.
FPDP long ; Saves DP value for just after FP op is compiled.
; This will be before the code is compiled to move
; the result from the FPU back to the stack or
; wherever. We will save FPDP in the descriptor
; and use it if we optimize.
FHeapChk
btst #flFP,opFlags ; Is operand on the floating heap?
bne.s .fhOut ; No
move.b FPA,d0 ; Yes. Get which A reg to use for dereference
bne.s .fh1
tst.w RevOpnds-hbase(a4)
bne.s .fh1
or.b #1,FPdispFlg-hbase(a4) ; If source, remember to dispose it
.fh1 bsr FetchToA
move.b #mdBD,opMode ; Change source descriptor to (An)
move.b FPA,opBreg
move.b #1,opind
clr.l opDispl
move.b #fbFP,opFlags
or.b #1,FPA-hbase(a4)
.fhOut rts
.opFP
; clr.b FPA-hbase(a4)
; tst.w RevOpnds-hbase(a4)
; beq.s .fp1
; exg a0,a1 ; Swap operands if necessary - for FP ops we
; can't incorporate this in the op itself
; NO - not doing this yet (or ever?)
; cmp.b #otFMOVE,Operation-hbase(a4)
; beq .opFPmove ; NOTE - in this case, CompMove should have
; been called, so we shouldn't need this.
; Now here's the real code!!!
.fp1 bsr.s FHeapChk ; Check source operand for FP heap. We
; have to do this first so that stack
; operands come out the right way around.
bsr FspecChk ; Check source for special
cmp.b #mdFPn,opMode(a1) ; Is dest FPn?
sne d7
beq.s .fp2 ; Yes (and leave D7 clear)
cmp.b #mdFPn,opMode ; No (and leave D7 set)
bne.s .fpTempR ; Is source FP0 or FP1?
cmp.b #1,opReg
bgt.s .fpTempR ; No - we have to use a temp FP reg.
; Yes- is op commutative?
cmp.b #otFPnoncom,operation-hbase(a4)
bge.s .fpTempR ; No - we'll use a temp anyway
exg a0,a1 ; Yes - swap operands
neg.b d7 ; Set d7 to 1 to show what we did
move.b #1,FPA-hbase(a4) ; The mem operand is really a destination
bra.s .fp2
.fpTempR push.l a0 ; We need to use a temp FP reg.
; Save src desc ptr
bsr newClrOD ; Get a new OD for FP0 descriptor
move.b #mdFPn,opMode
move.b #fbFP,opFlags
clr.b opReg
exg a0,a1
cmp.b #otFPmon,operation-hbase(a4)
bge.s .fpPop
move.b FPA,d6 ; Compile a move of the dest operand to FP0
bsr FPmove ; Save FPA no. used for any FP heap
; dereferencing in D6.
; clr.b FPA-hbase(a4) ; Ignore returned D0 flag as this operand
; mustn't be disposed from the FP heap
.fpPop pop.l a0 ; Restore src desc ptr
.fp2 bsr.s FHeapChk ; Check source operand again (may have
; changed)
moveq #0,d0
move.b Operation,d0
lsl.w #2,d0
lea xFPops-((otFPops)*4),a2
move.l 0(a2,d0.w),d0
cmp.b #mdFPn,opMode
bne.s .fpMem2reg
move.b opReg,d1
lsl.w #3,d1
bra.s .fpDstReg
.fpMem2reg
or.w #$4800,d0
swap d0
bsr EAbits
swap d0
moveq #0,d1
.fpDstReg
or.b opReg(a1),d1
lsl.w #7,d1
or.w d1,d0
push.l d0
jsr comma
bsr CompExt
bsr.s ChkFPdisp
push.l a1
get.l DP,FPDP-hbase(a4)
pop.l a1
; Now we do the final cleaning up.
tst.b d7 ; Is original destination FPn?
beq.s .fpEnd ; Yes - we're done
cmp.b #otFPcmp,operation-hbase(a4)
beq.s .fpEnd ; Also if it was a compare
move.l a1,a0 ; Move result to destination:
move.l 4(a6),a1 ; Restore original dest desc ptr to a1
btst #flFP,opFlags(a1) ; Is it on FP heap?
bne.s .fp4 ; No
; Yes - data addr will be in A0 or A1 as
; indicated by D6, as long as this was
; a dyadic op.
cmp.b #otFPmon,operation-hbase(a4)
bge.s .fpCUmon ; It wasn't
move.l a0,a1 ; It was.
UseODsrc
move.b #mdBD,opMode ; We use a desc specifying An
move.b #1,opind
exg a0,a1
; tst.w RevOpnds-hbase(a4)
; bne.s .useA0
; move.b #AnReg+1,opBreg(a1)
; bra.s .fp3
;.useA0 move.b #AnReg,opBreg(a1) ; Except if opnds were reversed, it's a0
add.b #AnReg,d6
move.b d6,opBreg(a1)
.fp3 clr.l opDispl(a1)
move.b #fbFP,opFlags(a1)
.fp4 bsr FPmove
.fpEnd tst.b d7
bpl .opRtn
bsr releaseOD ; Get rid of temp OD we allocated for FP0
bra .opRtn
.fpCUmon ; Clean up a monadic with result to heap
bsr ToNewHeap
bra.s .fpEnd
;.opFPmove
; bsr FPmove
; or.b d0,FPdispFlg-hbase(a4)
; bsr.s ChkFPdisp
; bra .fpEnd
ChkFPdisp
push.l a1
tst.b FPdispFlg-hbase(a4)
beq.s .cfdOut
bmi.s .cfd2
get.l ptrFPdisp,-(a6)
bra.s .cfdJSR
.cfd2 get.l ptrFPdisp2,-(a6)
.cfdJSR bsr CompJSRnoPush
.cfdOut pop.l a1
clr.b FPdispFlg-hbase(a4)
rts
; CompFPnew compiles a call to the routine to allocate a new FP heap block.
CompFPnew
push.l a1
get.l ptrFPnew,-(a6)
bsr CompJSRnoPush
pop.l a1
rts
; ======================
; OP2REG
; ======================
; Op2Reg recompiles the A0 pm-type descriptor to a Dn or An destination.
; D0 = n for Dn, AnReg+n for An. Leaves D0 with the actual D reg used.
; In several situations this will be different from the one requested.
; The caller will need to check for this, since the appropriate action
; varies. E.g. for IF we don't need to do anything more at all.
; Important note: DON'T DO BackDP on the same descriptor before calling Op2Reg!!
; We need the unbacked DP here, and will take care of it.
loc
ReqReg byte
align
Op2Reg
movem.l A0/A1,-(A6) ; Save
move.b D0,reqReg-hbase(A4)
move.b opShiftCnt,ShiftCnt-hbase(a4)
; We set up Operation below at .o2r2
bsr newClrOD ; Set up a new OD for the Dn/An dest
btst #6,D0 ; An requested?
bne.s .op2rAn
move.b #mdDn,opMode
bra.s .op2r1
.op2rAn move.b #mdAn,opMode
bclr #6,D0
.op2r1 move.b D0,opReg
move.b #1,opind
move.b #Lcode,opSize
move.l A0,A1
move.l (A6),A0 ; Recover A0 but leave on stack as well
moveq #0,D0
move.b (a0),d1 ; Opcode to D1
cmp.b #otCMP,d1
beq .op2rCmp ; If this op is a comparison
cmp.b #otRevSub,d1
seq revFlg-hbase(A4)
bne.s .o2r2
move.b #otSUB,d1 ; If rev sub, adjust opcode to normal sub
clr.w RevOpnds-hbase(A4)
.o2r2 move.b d1,operation-hbase(a4)
move.b opToFrom,d0 ; Is this op chained?
bmi.s .o2r3 ; No
; This op is a chained pm-type op.
cmp.b #fchChn,d0 ; Is it chained with a fetched operand?
bge .fchchn
move.b d0,ChnReg-hbase(a4) ; No
backDP
cmp.b #otSUB,d1
BNE.S .chn1 ; Is it subtract (not reversed)?
TST.B revFlg-hbase(A4)
BNE.S .chn1
PUSH.L xchnSub ; Yes - we need to switch operands. Compile
JSR comma ; sub.l (a6)+,d1
; neg.l d1
; Note: eventually we might have to use
; ChnReg to get the reg#, as in the FP
; code below, but at present it must be D1.
MOVEQ #1,D0 ; It was D1 we used
BRA .out
.chn1 MOVE.B #1,opReg(A1)
MOVE.B #mdDn,opMode(A1)
BSR newClrOD
MOVE.B #stkpop,opMode
BSR OP2 ; <op>.l (a6)+,d1
; Again it must always be D1 at present.
; Note also that if this op is monadic, OP2
; ignores the A0 descriptor, so that it
; just compiles
; <op>.l d1
; which is what we want.
BSR releaseOD
MOVEQ #1,D0
BRA .out
; Op is chained with a fetched operand.
.fchchn and.b #7,d0 ; Get chain reg #
move.b d0,ChnReg-hbase(a4)
move.b d0,opReg(a1)
move.b #mdDn,opMode(A1)
push.l a1
inc.l #-2,DP ; Just wipe out the move to the stk.
pop.l a1 ; Everything else was OK already.
bra .out
; This op is not chained. We now look for preceding fetches.
.o2r3 cmp.b #otMon,d1 ; If monadic op we only look for
bge.s .o2r4 ; one preceding fetch
downOD
cmp.b #otFetch,(a0)
bne .noF ; If no fetch precedes
.o2r4 downOD
cmp.b #otFetch,(a0)
bne.s .oneF ; If one fetch
; One fetch before a monadic op. This also used to handle two fetches before
; a dyadic, but as they are now already chained to the op, that case shouldn't
; arrive here. But if it does, it might still work. (Defensive programming?)
backDP ; We recompile as:
move.b reqreg,opToFrom
st ForceToR-hbase(a4)
bsr CompFetch ; MOVE.L <ea>,Dn
upOD
bra .callOP2 ; <OP>.L Dn
; One fetch before a dyadic op, or no fetch before a monadic.
.oneF upOD
backDP
TST.B revFlg-hbase(A4) ; Reverse subtract?
BNE.S .revSub1 ; Yes
bsr newOD ; No. First we load base for the
moveq #1,d0 ; fetched operand, since this could
bsr LoadBase ; pop something from the stack.
MOVE.B reqreg,D0
cmp.b #mdDn,opMode ; Does the fetch refer to same Dn?
bne.s .oneF1
cmp.b opReg,d0
bne.s .oneF1
UseODsrc
move.b #stkpop,opMode
move.b #Lcode,opSize
bsr OP2 ; Yes. Compile
bsr ReleaseOD ; <OP>.L <ea>,Dn
cmp.b #otSUB,operation-hbase(a4)
bne .windup
move.w #$4480,d0 ; and if op is subtract, compile
or.b reqreg,d0 ; NEG.L Dn
push.l d0
jsr wcomma
bra .windup
; No. Compile:
.oneF1 BSR CompPOPreg ; POP.L Dn
BRA.S .callOP2b ; <OP>.L <ea>,Dn
.revSub1 ; For rev sub we compile:
MOVE.B reqreg,opToFrom
st ForceToR-hbase(a4)
BSR CompFetch ; MOVE.L <ea>,Dn
UseODsrc
MOVE.B #stkpop,opMode
MOVE.B #Lcode,opSize
BRA.S .callOP2 ; SUB.L (A6)+,Dn
; No fetch precedes.
.noF
upOD
backDP
cmp.b #otSUB,operation-hbase(A4)
BEQ.S .sub ; Is the operation subtract?
.noF1 MOVE.B reqReg,D0 ; No. We'll recompile:
BSR CompPOPreg ; POP.L Dn
UseODsrc
MOVE.B #stkpop,opMode
MOVE.B #Lcode,opSize
BRA.S .callOP2 ; <OP>.L (A6)+,Dn
.sub ; Yes, it's subtract, which isn't
; commutative.
TST.B revFlg-hbase(A4) ; Is it actually reverse subtract?
BNE.S .noF1 ; Yes, operands are OK as they are.
MOVEQ #0,D0 ; No - needs special treatment. We
; recompile:
BSR CompPOPreg ; POP.L D0
MOVE.B reqReg,D0
BSR CompPOPreg ; POP.L Dn
UseODsrc
MOVE.B #mdDn,opMode
MOVE.B #Lcode,opSize
CLR.B opReg ; SUB.L D0,Dn
.callOP2
BSR newOD
MOVEQ #1,D0
BSR LoadBase
.callOP2b
BSR OP2
BSR releaseOD
.windup MOVEQ #0,D0
move.b reqreg,d0
CMP.B #mdAn,opMode(A1)
SEQ D1
AND.B #AnReg,D1
OR.B D1,D0
.out BSR releaseOD
MOVEM.L (A6)+,A0/A1 ; Restore
RTS
.op2rCmp
move.b reqreg,opToFrom ; Set chain flag in CMP desc
clr.b Rcond-hbase(a4)
push.l a1 ; Save ptr to Dn desc
bsr OptCmp
move.l (a6),a0 ; Recover for Scc compilation
bsr CompScc
compopl xextD1 ; Compile extends to get long boolean
pop.l a1 ; Restore Dn desc ptr ready for windup
bra.s .windup
; ========================
; FPOP2REG
; ========================
; FPop2Reg is the floating-point equivalent of Op2Reg. It recompiles the
; A0 FP-op descriptor to an FPn destination. Leaves D0 with the actual
; reg# used.
loc
.chkOpnd
; This is a subroutine to check if one of the operands involved
; in an FPop2Reg is the same as the FPn destination. If it is, we
; change the destination to FP1.
bsr CmpAddrs
bne.s .coOut
move.b #1,reqreg-hbase(a4)
move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags
move.b #1,opReg(a1)
.coOut rts
BackToFPDP
move.l opFPDP(a0),d0
push.l a1
get.l DP,d1
put.l d0,DP
pop.l a1
rts
FPop2Reg
movem.l a0/a1,-(a6) ; Save
moveq #0,d7 ; Will be set NZ if there's an FP temp
; to dispose at the end
move.b #1,ChnReg-hbase(a4) ; Chain is normally on FP1 - set as default
clr.b FPdispFlg-hbase(a4)
clr.b FPA-hbase(a4)
move.b d0,reqReg-hbase(A4)
bsr newClrOD ; Set up a new OD for the FPn dest
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b D0,opReg
move.l a0,a1
move.l (a6),a0 ; Recover A0 but leave on stack as well
moveq #0,d0
move.b (a0),d1 ; Opcode to D1
btst #0,1(a0)
sne revFlg-hbase(a4)
move.b d1,operation-hbase(a4)
move.b opToFrom,d0
bmi .fr3
; This op is chained.
cmp.b #fchChn,d0 ; Is it chained with a fetched operand?
bge .fchchn ; Yes
move.b d0,ChnReg-hbase(a4) ; No
move.b d0,reqreg-hbase(a4) ; Chain reg# will be the result reg,
backDP ; unless something happens to change it
cmp.b #otFPmon,operation-hbase(a4)
bge .chnMon
move.b #1,FPdispFlg-hbase(a4) ; Always an FP temp to dispose
cmp.b #otFPnoncom,operation-hbase(a4)
blt .chn1
tst.b revFlg-hbase(a4)
bne .chn1
; Non-commutative and not reversed - we need to switch operands. Current
; chain reg # is still in D0.
eor.b #1,d0 ; Compile:
push.w d0
bsr compPopFPn ; pop sequence to "other" FP reg FPn
moveq #0,d0
move.b Operation,d0 ; Fxxx FPm,FPn
lsl.w #2,d0
lea xFPops-((otFPops)*4),a2
move.l 0(a2,d0.w),d0
pop.w d2 ; Dest reg#
moveq #0,d1
move.b ChnReg,d1 ; Source reg#
move.b d2,ChnReg-hbase(a4) ; Changing chain to "other" reg
move.b d2,reqreg-hbase(a4) ; and the result will be there too
lsl.w #3,d1
or.w d2,d1
lsl.w #7,d1
or.w d1,d0
push.l d0
jsr comma
bsr chkFPdisp
bra .frEnd
; Commutative, or non-com but reversed. We can leave the operands as
; they are. Chain reg# is in D0.
.chn1 move.b d0,opReg(a1)
move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags(a1)
bsr newClrOD
move.b #stkpop,opMode
clr.b FPA-hbase(a4)
bsr OP2 ; Compile the right sequence, we hope
bsr releaseOD
bra .frEnd
; Op is monadic and chained.
.chnMon
move.b d0,opReg(a1) ; We're going to operate on the chain reg
move.b d0,reqreg-hbase(a4) ; So that's where the result will be
move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags(a1)
move.l a1,a0 ; Source and dest are both FPn, the chain reg.
bsr OP2 ; Compile the op
bra .frEnd
; Op is chained with a fetched operand.
.fchchn and.b #7,d0
move.b d0,ChnReg-hbase(a4)
move.b d0,reqreg-hbase(a4)
move.b d0,opReg(a1)
move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags(a1)
bsr BackToFPDP
bra .frEnd
; This op is not chained. We now look for preceding fetches. Opcode is in D1.
.fr3 cmp.b #otFPmon,d1 ; If monadic op we only look for
bge .frMonChk ; one preceding fetch
downOD
cmp.b #otFetch,(a0)
bne .noF ; If no fetch precedes
; One fetch before a dyadic op.
; bsr.s .chkOpnd ; %%%%
backDP
btst #flFP,opFlags
bne.s .fr4
st FPdispFlg-hbase(a4) ; 2 to dispose
bra.s .fr5
.fr4 move.b #1,FPdispFlg-hbase(a4) ; 1 to dispose.
.fr5 tst.b revFlg-hbase(A4) ; Reversed op?
bne.s .rev1 ; Yes
bsr newOD ; No. First load base for fetched operand
moveq #1,d0
bsr LoadBase
move.b reqreg,d0
cmp.b #mdFPn,opMode ; %%%start
bne.s .oneF1
cmp.b opReg,d0
bne.s .oneF1
cmp.b #otFPnoncom,operation-hbase(a4)
blt .stkSrc ; Compile
; (pop FP heap to An)
; <FOP> (An),FPn
moveq #1,d0
move.b d0,reqreg-hbase(a4)
move.b #mdFPn,opMode(a1)
move.b #fbFP,opFlags(a1)
move.b d0,opReg(a1)
.oneF1 ; Otherwise compile
bsr CompPopFPn ; (pop FP heap to FPn)
bra .callOP2b ; <FOP> <ea>,FPn
.rev1 ; For reversed op we recompile:
move.b reqreg,d0
bsr CompMoveToFPn ; FMOVE.L <ea>,FPn (or equivalent)
UseODsrc
move.b #stkpop,opMode ; (pop FP heap addr to a0)
move.b #1,opind
clr.b FPA-hbase(a4)
bra .callOP2 ; <FOP> (a0),FPn
; No fetch precedes.
.noF upOD
backDP
st FPdispFlg-hbase(a4) ; There will be two to dispose
cmp.b #otFPnoncom,operation-hbase(a4)
blt.s .nf1
tst.b revFlg-hbase(a4)
bne.s .nf1
; Not commutative and not reversed. We need to juggle.
moveq #0,d0
bsr CompPopFPn
move.b #1,FPA-hbase(a4)
move.b reqreg,d0
bsr CompPopFPn
UseODsrc
move.b #mdFPn,opMode
move.b #fbFP,opFlags
clr.b opReg
clr.b reqreg-hbase(a4) ; Result will be in FP0
bra.s .callOP2
; Commutative, or non-com but reversed. Operands are OK as they are.
.nf1 move.b FPA,d0
move.b reqreg,d0
bsr CompPopFPn
.stkSrc UseODsrc
move.b #stkpop,opMode
.callOP2 ; Remember to set FPA appropriately before
; coming here!
bsr newOD
move.b FPA,D0
bsr LoadBase
.callOP2b
bsr OP2
bsr releaseOD
.frEnd bsr releaseOD
moveq #0,d0
move.b reqreg,d0 ; Reqreg will have been changed appropriately
; if we used a different reg than the one
; requested.
movem.l (a6)+,a0/a1 ; Restore
rts
; This is a monadic op.
.frMonChk
downOD
cmp.b #otFetch,(a0)
bne.s .frMonNoF
; One fetch before a monadic op. We can absorb the fetch into the operation.
backDP
; move.b reqreg,d0
; bsr CompMoveToFPn
move.l a0,a1
bsr newClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b reqreg,opReg
exg a0,a1
bsr OP2 ; <FOP> <ea>,FPn
bsr releaseOD
bra .frEnd
; No fetch before a monadic op.
.frMonNoF
upOD
backDP
bsr newClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b reqreg,opReg
move.l a0,a1
bsr newClrOD
move.b #stkPop,opMode
move.b #1,opind
bsr OP2 ; <FOP> <ea>,FPn
bsr releaseOD
bsr releaseOD
bra .frEnd
; ============================
; GETBASE etc.
; ============================
; GetBase converts the address in D0 to base-displacement form.
; At this stage we maintain a couple of abstractions - we use A3 (lobase)
; for all main dictionary references, and A5 for modules, and we keep a
; 4-byte displacement. We defer the decision as to whether we'll actually
; use A4 (hibase) for a main dic reference until the final code generation,
; since the displacement may be modified first, which could change things.
; We leave the base reg # in D1, and the displacement in D0. Other regs preserved.
loc
getBase
push.l a1 ; Save A1
get.L MBcomp,a1 ; Modbase value for compilation to A1
move.l d0,d1
add.l #32766,d1
cmp.l a1,d1
bhs.s .gbMod ; No - use modbase
get.L State,D1 ; Are we in compile state,
beq.s .gb1
get.L SAcomp,D1 ; and compiling stand-alone code?
BNE.S .SAfail ; Yes - fail - can't ref main dic from there
.gb1 sub.l a3,d0 ; No. Make displ relative to lobase
moveq #3,d1
bra.s .gbOut
.gbMod sub.l a1,d0
moveq #5,d1
.gbOut pop.l a1 ; Restore A1
rts ; and get out.
.SAfail move.l #160,d0 ; "You can't refer to the main dic from
bra hndErr ; stand-alone code"
; setAddr ( addr -- ) Sets up the A0 descriptor with the corresponding base
; and displacement.
loc
setAddr pop.l d0
move.l d0,opAddr
bsr.s getBase
bset #6,d1
move.b d1,opBreg
move.l d0,opDispl
move.b #mdBD,opMode
move.b #1,opind ; This is usually what we want
rts
; offsetAddr applies the offset in D0 to the address in the A0 descriptor,
; modifying the descriptor as necessary. It's not very complicated now, but
; it used to have to check for a base reg change.
offsetAddr
add.l d0,opDispl
add.l d0,opAddr
rts
loc
; GetRealBase is called to adjust a "virtual" base reg to the real one
; once actual code is to be compiled.
; Entered with D0 and D1 as returned from a GetBase call, i.e.
; D0 = displacement, D1 = virtual base. Also D2 = absolute address.
; Returns D0 = displacement, D1 = real base. Leaves CC = NE if the result
; can't be compiled into a single instruction. These cases are:
; 1. The displacement is too big for 16 bits.
; 2. The base specified is modbase (A5) but the use of modbase has been
; inhibited by InhibitMB?.
GetRealBase
movem.l d3/a1,-(a6) ; Save regs
cmp.b #3,d1
beq.s .grbMainDic
cmp.b #4,d1 ; Virtual base shouldn't really be A4, but
beq.s .grbMainDic ; this will get the right answer if it is.
cmp.b #5,d1
bne.s .grbWC ; If some other base reg, leave alone and just
; check the displ for range.
get.l inhibitMBq,d3 ; It's A5. If usage isn't inhibited,
; range chk and out.
beq.s .grbWC
moveq #noReg,d1 ; If it is, return "no reg" and CC = NE.
bra.s .grbOut
.grbWC bsr WdChk
.grbOut movem.l (a6)+,d3/a1 ; Restore regs (preserves CC)
rts
.grbMainDic
lea 32766(a3),a1
cmp.l a1,d2
bhs.s .grbUseHB
moveq #3,d1
move #4,ccr ; Use lobase. Set CC EQ.
bra.s .grbOut
.grbUseHB ; Use hibase.
move.l d2,d0
sub.l savedA4,d0
moveq #4,d1
bra.s .grbWC ; Leave CC EQ if OK, NE if out of range
; ========================
OpAndAddr ; ( addr -- ) Opcode is in D0.
MOVE.L D0,D6 ; Opcode
BSR initODs
BSR setAddr
moveq #0,d0
bsr loadbase
MOVE.L D6,D0
BRA CompMOp2 ; Note: assumes Size bits OK already
; ==========================
; COMPJSR
; ==========================
; COMPJSR ( addr -- ) is the basic routine to compile a plain vanilla call
; to a non-inline Mops word. For efficiency, we use BSR short form if we can.
; If the target is too far away, we try to use an An-relative JSR, which
; simplifies code movement. But if the target is outside base addressing
; range, we can't do that, so we try to generate a long BSR. If all else
; fails we use an index-mode JSR.
loc
compJSR
st d0 ; True means we push the JSR desc at the end
BSR initODs
.cj0 lea ODnew,a0
.cjDP ; CompJSRnoPush comes in here
movem.l d3-d7,-(a7) ; Save
move.b d0,d3 ; Transfer push flag to D3
get.L dp,d0
bsr GetBase ; Get base for where we are now
move.b d1,d4 ; Save in d4
move.l (a6),d0
bsr GetBase ; Get base for target
move.b d1,d7 ; Save base in d7
move.l d0,d6 ; and displacement in d6
cmp.b d1,d4 ; Compare dic segments
bne.s .jsr ; If different, can't use BSR!!
.cjBsr compop xbsr ; We'll try for a short BSR first
move.l (a6),d5 ; dest addr
get.L dp,D0
sub.l d0,d5 ; Branch offset to D5
cmpi.l #-128,d1 ; Can we do a short BSR?
blt.s .long ; No
cmp.l #128,d1
bge.s .long ; No
addq #4,A6 ; Yes
get.B fmkCnt,D2
put.B D2,callOut
move.l d0,a0
move.b d1,-1(a0)
bra.s .cjEnd
; We're out of range of a short branch. We try for an An-relative JSR.
.long move.l d6,d0
move.b d7,d1
move.l (a6),d2
bsr getRealBase ; Can we do it?
bne.s .cjPC ; No
.toJSR inc.L #-2,dp ; Yes. Wipe BSR and fall thru
; to JSR code.
.jsr move.b #mdBD,opMode
move.b #1,opind
bset #6,d7
move.b d7,opBreg
move.l d6,opDispl
move.l (a6)+,opAddr
bsr newOD
moveq #1,d0 ; We'll LoadBase to A1, since if this
bsr loadbase ; is a method call, ^obj will be in A0 (the
move.w #$4E80,d0 ; voice of experience)
bsr CompMOp2
bsr releaseOD
bra.s .cjEnd
.cjPC move.l d5,d0 ; Can't do An-rel JSR.
bsr WdChk ; Can we do PC-rel BSR?
bne.s .toJSR ; No - do JSR anyway, as LoadBase will
; convert to index mode.
addq #4,a6 ; Yes
push.l d5
jsr wcomma
get.B fmkCnt,D2
put.B D2,callOut
.cjEnd tst.b d3
beq.s .noPush
movem.l (a7)+,d3-d7 ; Restore
get.W saveTandS,D0
bne.s .getout
lea ODnew,a0
btst #flFP,opFlags
bne.s .psh
move.b #otJSR,(a0) ; If not a FP op, force desc type to JSR
.psh bra pushOD
.noPush movem.l (a7)+,d3-d7
.getout rts
CompJSRnoPush ; As for CompJSR, but uses the a temp desc rather
move.l a0,-(a7) ; than ODnew, and doesn't push the JSR desc.
bsr newOD
sf d0
bsr .cjDP
bsr releaseOD
move.l (a7)+,a0
rts
CompJsrLong
MOVE.W #$4E80,D0
BSR OpAndAddr
bra.s .cjEnd
; hPatch ( newCfa oldCfa -- ) is called to handle a FORWARD definition.
; It compiles a JMP to newCfa at oldCfa, so that a call to oldCfa will
; in fact execute newCfa. 10 bytes must be available at oldCfa to
; accommodate the longest JMP sequence.
hPatch
get.l dp,-(a7) ; Save DP
put.l (a6)+,dp ; Set DP to oldCfa
move.w #$4EC0,d0 ; Compile a JMP to newCfa there
bsr OpAndAddr
put.l (a7)+,dp ; Restore DP
rts
; JSRtoJMP recompiles a JSR as a JMP, or a BSR as a BRA.
; Leaves CC NE if succeeded. If the instruction at opDP isn't
; a JSR or BSR, we assume we have a more complex sequence (which
; can happen if we're outside normal addressing range. We don't
; do anything in this case, but return CC EQ.
JSRtoJMP
loc
move.l opDP,a1
move.w (a1),d0
and.w #$FFC0,d0
cmp.w #$4E80,d0
beq.s .jsr
and.w #$FF00,d0
cmp.w #$6100,d0
beq.s .bsr
move #4,ccr
rts ; Return with CC EQ
.jsr or.b #$40,1(A1)
rts ; Return with CC NE
.bsr move.b #$60,(a1)
rts ; Ditto
; =============================
; COMPILATION OF ENTRY AND EXIT
; =============================
hmentry
PUSH.L xMentry
JSR comma
RTS
loc
XLdispl long
XLeaBits word
numLoc long
numPLadj long
RegNumAdjustment
long
; FPadjust is a utility routine to adjust the #PL count in D6 - if we're compiling FPU
; code, FP locals will go to the FP regs (or as many as possible will, anyway).
; This will free up some D regs, in effect reducing the value of #PL which is used
; to calculate the register allocation.
; This routine also sets RegNumAdjustment to the (positive) value by which #PL is
; reduced.
; Uses D0-D4.
FPadjust
clr.l RegNumAdjustment-hbase(a4)
get.l useFPUq,d0
beq.s .fpaOut
get.l FltFlg,d4
move.l d6,d1
move.l numLoc,d3
beq.s .fpaOut
moveq #6,d2
bra.s .fpaLpTst
.fpaLoop
lsr.l #1,d4
bcc.s .fpaLpTst
subq.l #1,d6
subq #1,d2
beq.s .fpaOut
.fpaLpTst
dbra d3,.fpaLoop
sub.l d6,d1
move.l d1,RegNumAdjustment-hbase(a4)
.fpaOut rts
; PLentry calls HPLentry. This compiles the entry sequence for a word or method
; with named parameters and/or local variables. It uses the values #P (the number
; of parameters), #PL (the total number of parameters plus locals), #F (the number
; of floating parms/locals) and FltFlg (a 4-byte indicator of which parms/locals
; are floating, one bit for each one).
hplentry
get.l numPL,d6 ; D6 = numPL
get.l numP,d5 ; D5 = numP
move.l d6,d0
sub.l d5,d0 ; Work out number of locals
move.l d0,numLoc-hbase(a4) ; and save it
bsr FPadjust ; Adjust D6 if necessary
move.l d6,numPLadj-hbase(a4)
; Save phase. Here we compile code to save all the regs and XL locations we need,
; on the return stack.
moveq #0,d4 ; D4 will hold no of ExtraLocals (XL) locations
moveq #4,d2
sub.l d6,d2 ; D2 = number of unused reg slots
bpl.s .sp1 ; Positive - we won't be using the XL area
; We will be using the XL area.
sub.l d2,d4 ; D4 = number of XL locations
moveq #0,d2 ; No unused reg slots
cmp.b #3,d4 ; Is # XL locns gtr than 3?
ble.s .sp1 ; No - no need to save D3
move.w #$F8,d1 ; Yes - include D3 in initial reg save mask
bra.s .sp2
.sp1 move.w #$F0,d1 ; No need to save D3, so here we don't
; include it in initial reg save mask.
.sp2 move.w d1,d0
lsr.w d2,d0
and.w d0,d1
moveq #$27,d0 ; ea bits for -(a7)
moveq #2,d2 ; Predecrement, reg to mem
bsr compMOVEM ; movem.l <regs>,-(a7)
tst.w d4
beq.s .spsvF
lea XLOD,a0
move.l d4,d0
asl.l #2,d0
add.l XLDispl,d0
move.l d0,opDispl
.spXLloop ; Loop to move XL locations to rtn stack,
move.w #$3FF,d1 ; 10 at a time. Initial reg save mask to D1,
; specifying D0-D7/A0/A1
sub.w #10,d4
bgt.s .spx1
; Last time round.
move.l XLdispl,opDispl ; Restore opDispl of XL start for source
cmp.w #-9,d4 ; Only one left?
beq.s .spMv1 ; Yes
move.w d4,d2 ; No
neg.w d2
lsr.w d2,d1 ; Adjust MOVEM mask
bra.s .spx2
.spMv1 move.w XLeaBits,d0 ; Only 1 to be moved - use MOVE instead
or.w #$2F00,d0 ; of MOVEM
push.l d0
jsr wcomma
bsr CompExt
bra.s .spsvF
.spx1 sub.l #40,opDispl ; Not last time. Decrement XL addr by 40.
.spx2 move.w XLeaBits,d0
moveq #1,d2
bsr CompMOVEM ; movem.l <XL+n>,d0-d7/a0/a1
moveq #$27,d0 ; ea bits for -(a7)
moveq #2,d2 ; Predecrement, reg to mem
bsr compMOVEM ; movem.l d0-d0/a0/a1,-(a7)
tst.w d4
bgt.s .spXLloop
.spsvF get.l useFPUq,d0 ; Now we save the FP regs we need.
beq.s .parmPhase ; Skip this if not compiling FPU code
get.l numF,d0 ; Or if no floating parms/locals
beq.s .parmPhase
move.w #$FC,d1 ; fmovem mask for FP2-FP7
moveq #6,d2
sub.w d0,d2 ; Work out how many we really need to save
ble.s .spsvFmany ; If all
move.w d1,d0 ; Not all - shift and mask the mask
lsr.w d2,d0
and.w d0,d1
bsr LowBit
blt.s .spsvFone
.spsvFmany
move.l #$F227E000,d0 ; fmovem <regs>,-(a7)
or.w d1,d0
push.l d0
jsr comma
bra.s .parmPhase
.spsvFone
push.l #$F2276900,d0 ; fmove.x FP2,-(a7)
jsr comma
; Parm phase. Here we compile code to move the stack parms into the regs and/or
; the XL area.
.parmPhase
tst.w d5 ; Test #P
beq .ppinitF ; If no parms, skip this
move.w d6,d4
sub.w d5,d4 ; D4 = # locals
subq.w #4,d4
bge .pp2 ; If locals will use all the regs
move.w #$F00,d1
neg.w d4 ; D4 = # D regs available for parms
lsr.w d4,d1
and.w #$FF,d1 ; Form reg mask for those regs
move.w d4,d0
sub.w d5,d0 ; Check how many we really need
ble.s .pp1
move.w #$F0,d2 ; If not all, mask out the ones we don't need
lsr.w d0,d2
and.w d2,d1
; Final mask is in D1.
.pp1 moveq #$1E,d0 ; ea bits for (a6)+
moveq #1,d2 ; not predecrement, mem to reg
bsr CompMOVEM ; movem.l (a6)+,<regs>
move.w d5,d0
sub.w d4,d0 ; D0 = # parms going to XL area
ble.s .ppinitF ; If none
move.w d0,d4
.ppXLloop ; Loop to pop parms to XL locations,
move.w #$30F,d1 ; 6 at a time - that's how many regs we have
sub.w #6,d4 ; available for scratch use. If we need 4 or
; more XL locations D3 will have been saved,
; and so is available here.
bgt.s .ppx1
; Last time round.
cmp.w #-5,d4 ; Only one left?
beq.s .ppMv1 ; Yes
move.w d4,d2 ; No
neg.w d2
move.w #$33F,d3
lsr.w d2,d3 ; Adjust MOVEM mask - as the number of regs to
and.w d3,d1 ; be moved gets less, we omit A1, then A0,
bra.s .ppx1 ; then D3, then D2. At least D0 and D1 must
; be moved, or we wouldn't have got here!
.ppMv1 move.w XLeaBits,d0 ; Only 1 to be moved - use MOVE instead
move.l d0,d1 ; of MOVEM
and #7,d0
lsl #6,d0
and #$38,d1
or d1,d0
lsl #3,d0
or.w #$201E,d0
push.l d0
jsr wcomma
bsr CompExt
bra.s .ppinitF
.ppx1 moveq #$1E,d0 ; ea bits for (a6)+
moveq #1,d2 ; not predecrement, mem to reg
bsr CompMOVEM ; movem.l (a6)+,d0-d2/a0/a1
move.w XLeaBits,d0
moveq #0,d2 ; Not predecrement, reg to mem
bsr compMOVEM ; movem.l d0-d2/a0/a1,<XL+n>
add.l #24,opDispl ; Increment XL displ by 6*4 = 24
tst.w d4
bgt.s .ppXLloop
bra.s .ppinitF
.pp2 asl.w #2,d4 ; We come here if all regs are in use for locals.
ext.l d4
add.l d4,opDispl ; Update opDispl to 1st XL locn for parms
move.w d5,d4
bra .ppXLloop ; Go to loop to move parms to XL area.
; Now we compile code to initialize any floating point parms or locals.
; For non-FPU code, floating locals are cleared. For FPU code, floating
; parms have to be moved to the FP regs.
.ppinitF
get.l numPL,d1 ; Unadjusted #parms/locals to d1 for loop count
move.l numLoc,d2 ; # locals to d2
moveq #4,d3 ; d3 will keep track of D reg usage
moveq #1,d7 ; d7 will keep track of FP reg usage
get.l FltFlg,d4 ; FltFlg has a bit for every floating p/l
.ppFloop
lsr.l #1,d4 ; Is next p/l floating?
bcc.s .ppFlptst ; No
get.l UseFPUq,d0 ; Yes. Using FPU?
bne .ppFPU ; Yes
.notFPn tst.w d2 ; No. Are we still doing locals?
bgt.s .clear ; Yes - clear this one
.ppFlptst ; Test for loop end:
addq #1,d3 ; Update D reg #
.ppFlptst1 ; FPU local code comes in here - D reg not
; used for FP local if FP reg is available.
subq #1,d2
subq #1,d1 ; Any parms/locals left?
bgt.s .ppFloop ; If so, loop
.ppOut rts
.clear move.l d3,d0 ; Get D reg #
subq.l #8,d0 ; If >= 8, it's really in the XL area
bge.s .clXL
move d3,d0
moveq #0,d1
bsr CompMoveq ; moveq #0,Dn
bra.s .ppFlptst
.clXL asl.l #2,d0 ; Local is in XL area.
add.l XLdispl,d0 ; Work out right opDispl
move.l d0,opDIspl
move.w XLeaBits,d0
or.w #$4280,d0
push.l d0
bsr wcomma ; clr.l <ea>
bsr compExt
bra.s .ppFlptst
.ppFPU addq.w #1,d7 ; We're compiling FPU code.
cmp.w #7,d7 ; Any FP regs left?
bgt .notFPn ; No - handle as if non-FPU code.
tst.w d2 ; Yes. Doing parms yet?
bgt .ppFlptst1 ; No - nothing to do.
movem.l d1-d4,-(a6) ; Yes - save regs
bsr newClrOD ; Parm to be moved to FPn.
move.b #mdFPn,opMode ; New temp OD for FPn
move.b #fbFP,opFlags
move.b d7,opReg ; Set FP reg #
move.l a0,a1
clr.b FPA-hbase(a4)
move.l d3,d0 ; Get D reg number for parm
subq.l #8,d0 ; If >= 8, it's really in the XL area
bge.s .ppFPXL
bsr newClrOD
move.b #mdDn,opMode
move.b #1,opind
move.b d3,opReg
bsr CompMove ; Move operand to FPn
bsr releaseOD
bsr releaseOD
.ppFPrstr
movem.l (a6)+,d1-d4
bra .ppFlptst
.ppFPXL lea XLOD,a0
asl.l #2,d0
add.l XLdispl,d0
move.l d0,opDIspl
bsr CompMove
bsr releaseOD
bra.s .ppFPrstr
; ============ Compilation of exit =============
; This code is basically the reverse of the save phase above. It restores everything
; that got saved.
rstRegMask word
CompExit
get.l localq,D0 ; Skip the following if we're in a local
bne .ce1 ; section
get.l numPL,d0
beq .ce1 ; Or if there are'nt any parms/locals
get.L FltFlg,d1 ; FltFlg marks any floating parms/locals
beq.s .ceFPrst ; Skip this if none
get.l useFPUq,d0
bne.s .ceFPU ; If we're compiling FPU code, special
; treatment
; Now we dispose of floating heap locations.
.ceDsp lea ODnew,a0
bsr ClearOD
move.b #mdLit,opMode
move.l d1,opLit ; Compile a literal fetch of FltFlg value to D2
moveq #2,d0
bsr FetchToD
; move.l numLoc,opLit
; moveq #1,d0 ; And number of locals to D1
; bsr FetchToD
get.l ptrLFdisp,-(a6) ; JSR LFdisp-base(a3)
bsr CompJSRnoPush
bra.s .ceFPrst
.ceFPU ; Using FPU. We only have to dispose if
; some parms/locals didn't fit in the FP regs.
moveq #5,d0
moveq #0,d2
.ceFloop
lsr.l #1,d1 ; Look at bit for next p/l - floating?
bcs.s .ceF
addq #1,d2 ; No - count non-floating operands
bra.s .ceFloop ; and loop
.ceF beq.s .ceFPrst ; Yes, but it was the last one, so we're done
dbra d0,.ceFloop ; Not the last one. Loop if any FP regs left.
lsl.l d2,d1 ; None. Shift FltFlg back - now it only flags
bra.s .ceDsp ; Dn/XL floating operands, with strictly
; one bit per D reg or XL locn. We dispose
; them as for non-FPU code.
; Now we must restore any FP regs used.
.ceFPrst
get.l useFPUq,d0
beq.s .ce0
get.l numF,d0
beq.s .ce0
move.w #$3F,d1 ; fmovem mask for FP2-FP7
moveq #6,d2
sub.w d0,d2 ; Work out how many we really need to restore
ble.s .ceFmany ; If all
move.w d1,d0 ; Not all - shift and mask the mask
lsl.w d2,d0
and.w d0,d1
bsr LowBit
blt.s .ceFone
.ceFmany
move.l #$F21FD000,d0 ; fmovem (a7)+,<regs>
or.w d1,d0
push.l d0
jsr comma
bra.s .ce0
.ceFone push.l #$F21F4900,d0 ; fmovem (a7)+,FP2
jsr comma
; Now we'll restore the XL locations and D regs which have been saved on the
; return stack in the save phase.
.ce0 lea XLOD,a0
move.l XLdispl,opDispl
move.w #$F0,rstRegMask-hbase(a4) ; Initial MOVEM mask for restoring D regs
move.l numPLadj,d6 ; D6 = numPL, adjusted
get.l numP,d5 ; D5 = numP
moveq #0,d4 ; D4 will hold no of ExtraLocals (XL) locations
moveq #4,d2
sub.l d6,d2 ; D2 = number of unused reg slots
bpl.s .ceRegs ; Positive - we didn't use the XL area
; We did use the XL area.
sub.l d2,d4 ; D4 = number of XL locations
cmp.b #3,d4 ; Greater than 3?
ble.s .ceXLloop
move.w #$F8,rstRegMask-hbase(a4) ; Yes - include D3 in mask for restoring regs
.ceXLloop ; Loop to pop rtn stk to XL locations
move.w #$3FF,d1 ; 10 at a time.
sub.w #10,d4
bgt.s .cex1
; Last time round.
cmp.w #-9,d4 ; Only one left?
beq.s .ceMv1 ; Yes
move.w d4,d2 ; No
neg.w d2
lsr.w d2,d1 ; Adjust MOVEM mask
bra.s .cex1
.ceMv1 move.w XLeaBits,d0 ; Only one to be moved. Use MOVE instead
move.l d0,d1 ; of MOVEM
and #7,d0
lsl #6,d0
and #$38,d1
or d1,d0
lsl #3,d0
or.w #$201F,d0
push.l d0
jsr wcomma
bsr CompExt
bra.s .ceAllRegs
.cex1 ; Not last time.
moveq #$1F,d0 ; ea bits for (a7)+
moveq #1,d2 ; Not predecrement, mem to reg
bsr compMOVEM ; movem.l (a7)+,d0-d7/a0/a1
move.w XLeaBits,d0
moveq #0,d2 ; Not predecrement, reg to mem
bsr CompMOVEM ; movem.l d0-d7/a0/a1,<XL+n>
add.l #40,opDispl ; Increment XL addr by 40 for next time round
tst.w d4
bgt.s .ceXLloop
.ceAllRegs ; Finished restoring XL area.
moveq #0,d2
.ceRegs move.w rstRegMask,d1 ; Now we restore the D regs.
move.w d1,d0
lsr.w d2,d0 ; D2 = number of unused regs.
and.w d0,d1
moveq #$1F,d0 ; ea bits for (a7)+
moveq #1,d2 ; Not predecrement, mem to reg
bsr compMOVEM ; movem.l (a7)+,<regs>
.ce1 get.l numLast,d0 ; If any CallLast calls to be made, we
bne.s .ceOut ; get out - caller will handle
hDefnEnd ; Entry point called by ;m to compile the actual end
; of a definition if there were any CallLast calls,
; since these had to come first.
get.b Methodq,D0 ; Method?
beq.s .ce3 ; No
compop xRpopA2 ; Yes - compile MOVE.L (A7)+,A2 to restore A2
.ce3 get.b colaFlg,d0 ; :A definition?
beq.s .ce4 ; No
get.l MBcomp,d0 ; Are we actually compiling a module?
addq.l #1,d0
beq.s .ce4 ; No
compop xRpopA5 ; Yes - compile move.l (a7)+,a5
; move.l colaFlg,a0
; sf (a0)
.ce4 lea OD,a0
cmp.b #otJSR,(a0)
bne.s compRTS
bsr JSRtoJMP
beq.s compRTS
.ceOut rts
compRTS
compop xRTS
rts
; hColA handles the entry sequence for :A words. These need to push A5
; on to the return stack at the start, then set A5 to where MBcomp points
; at compile time. :A words are needed for exported classes, since an object
; can exist in one module whose class is implemented in another module. If one
; of the methods executes an action handler or whatever that is a part of the
; object, and which lives in the same module as the object (which is quite normal),
; A5 will be wrong when the action handler executes. :A and ;A solve
; this problem, by temporarily restoring A5 to its right value for the module
; containing the :A word.
hColA loc
move.l colaFlg,a0
st (a0)
get.l MBcomp,d0 ; Are we actually compiling a module?
move.l d0,d1
addq.l #1,d1
beq.s .out ; If not, skip the rest
compop xRpshA5 ; Compile move.l a5,-(a7)
get.l inhibitMBq,-(a6) ; Save inhibit modbase flag
moveq #-1,d1 ; and set it true - in setting A5 to the
move.l d1,(a1) ; MBcomp value, we mustn't use A5 in the
push.l d0 ; addressing step, since it probably won't
bsr saveOD ; be valid (this is why we're using :A after
bsr SetAddr ; all).
clr.b opind
move.b #AnReg+5,opToFrom
bsr CompFetch
put.l (a6)+,inhibitMBq
.out rts
; ====================================
; NAMED PARAMETERS AND LOCAL VARIABLES
; ====================================
GetRegNum ; Utility routine to get the Dn reg number for the current
loc ; parm or local. If really Dn, returns reg# in D0, and
; leaves CC NE. If really in the XL area, returns
; the XL item number in D0, and leaves CC EQ.
; Uses D1-D4.
get.l locno,d0
get.l useFPUq,d1
beq.s .grn1
cmp.l numLoc,d0
blt.s .grnLoc
sub.l RegNumAdjustment,d0
.grn1 subq.l #4,d0 ; Is it in Dn or XL area?
bge.s .grnXL
addq.l #8,d0 ; Dn. n to d0. Can't be 0, so
rts ; sets CC NE as well.
.grnXL move #4,CCR
rts
.grnLoc
get.l FltFlg,d4
move.l d0,d1
moveq #6,d2
bra.s .grnLpTst
.grnLoop
lsr.l #1,d4
bcc.s .grnLpTst
subq.l #1,d0
subq #1,d2
beq.s .grn1
.grnLpTst
dbra d1,.grnLoop
bra.s .grn1
GetFPnum ; Utility routine to get the reg number for the current
; FP parm/local. If FPn, we return reg# in D0, and
; leave CC NE. If Dn, (i.e. an FP heap addris in Dn), we
; return the D reg no in D0 and leave the CC EQ.
; If in the XL area, we leave D0 = -1, return
; the XL item number in D1, and leave CC EQ.
; Uses D1-D4.
get.l locno,d0
get.l useFPUq,d1
beq .gfNotFPn ; If we're not compiling FPU code, operand
; isn't in FPn.
move.w d0,d1 ; D1 will be loop counter
get.l FltFlg,d4
beq.s .gfErr ; OK OK, so I'm a suspicious character.
moveq #0,d0 ; D0 will count "D regs"
moveq #6,d2 ; D2 will count down # of free FP regs
move.l NumLoc,d3 ; D3 will count down # of locals
bra.s .gfTst
.gfLoop lsr.l #1,d4
bcs.s .gfFP
subq #1,d3
.gfDinc addq #1,d0 ; Increment D reg count
bra.s .gfTst
.gfFP subq #1,d2 ; FP operand. Any FP regs left?
blt.s .gfDinc ; No. Inc D reg count
subq #1,d3 ; Yes. Still doing locals?
blt.s .gfDinc ; No. Inc D reg count
.gfTst dbra d1,.gfLoop
tst.w d2
ble.s .gfNotFPn
moveq #8,d0
sub.w d2,d0 ; Get FP reg no to D0. Must set CC NE.
rts
.gfNotFPn ; Operand isn't in FPn.
subq.l #4,d0 ; Is it in Dn or XL area?
bge.s .gfXL
addq.l #8,d0 ; Dn. n to d0.
move #4,CCR ; Set CC EQ as well.
rts
.gfXL move.l d0,d1
moveq #-1,d0
move #4,CCR
rts
.gfErr dc.w $FFE7
hndlr loc_h,0 ; loc_h
addq #4,a6 ; Don't need address of dummy LOCPARM
bsr GetRegNum
beq.s .locXL
move.b #mdDn,d4
moveq #0,d5
; Floating locals come in here.
locH
bsr SaveOD
move.b d0,opReg ; Set up ODnew
move.b d4,opMode
move.b d5,opFlags
loc2 move.b #1,opind ; Contents of the reg is the operand
loc3 tst.w opcode+2-hbase(a4) ; From now on it's the same as a Value,
beq ftchVal1 ; but we don't call SetAddr.
bra stVal1
.locXL asl.l #2,d0
add.l ExtraLocals,d0
push.l d0
bra valH
; =======================
; Handler for floating named parms or locals.
flODaddr long
hndlr Floc_h,0 ; Floc_h
addq #4,a6 ; Don't need address of dummy FLOCPARM
sf FatStq-hbase(a4)
bsr GetFPnum
beq.s .flNotFPn
move.b #mdFPn,d4
move.b #fbFP,d5
bra.s locH
.flNotFPn
st Flocq-hbase(a4) ; Not in FPn. Not much point in optimizing.
tst.w d0 ; Where is the operand?
bmi .flXL
st d7 ; Dn. Set flag. D reg no is in D0.
move.l d0,d4 ; Also save in D4
tst.l opcode-hbase(a4) ; Fetching or storing?
bne.s .flst
; Fetching.
or.w xMvD0A1,d0 ; Convert to MOVE.L Dn,A1
push.l d0
jsr wcomma ; Compile that.
.fl2 get.l ptrLfloat,-(a6) ; JSR Lfloat-base(a3)
bsr CompJSRnoPush
tst.b d7
beq releaseOD
rts
; Storing.
.flst or.w xMvD0D2,d0 ; MOVE.L Dn,D2
push.l d0
jsr wcomma ; Compile that
.flst1 get.l ptrToLfloat,-(a6)
flst2 move.l opcode,d1 ; Storing or operating to the flt loc?
cmp.b #otStore,d1
blt.s .fop
moveq #-1,d1 ; Storing. We use -1 as opcode for ToLfloat
bra.s .fmvq
.fop lsl.w #1,d1 ; Other operation (add or whatever).
lea xSANE-((otFPops+1)*2),a0
move.w 0(a0,d1.w),d1 ; Get SANE opcode
.fmvq moveq #1,d0 ; Compile:
bsr CompMoveq ; MOVEQ #<opcode>,D1
bsr CompJSRnoPush ; JSR <wherever>
tst.b Flocq-hbase(a4) ; Local/parm or Fvalue?
beq.s .flOut ; Out if Fvalue
tst.b d7 ; Local/parm. In Dn or XL area?
beq.s .flstXL
ror.w #7,d4 ; Dn - we compile:
or.w xMvD2D0,d4
push.l d4
jmp wcomma ; MOVE.L D2,Dn
.flstXL move.l flODaddr,a0 ; XL area. Compile a store from D2 to the
move.b #2,opToFrom ; XL location.
move.b #otStore,(a0)
bsr CompStore
bra releaseOD ; We will have been using a temp OD, so we
; release it
.flOut rts ; Fvalue - we're done
.flXL sf d7 ; Here we set up for an op on a floating
asl.l #2,d1 ; parm/local in the XL area. Item# is in D1.
add.l ExtraLocals,d1 ; XL addr to D1
push.l d1
bsr newClrOD ; We need a temp OD for this, as ODnew
; gets used
move.l a0,flODaddr-hbase(a4) ; Save temp OD addr
bsr SetAddr
tst.l opcode-hbase(a4) ; Fetching or storing?
bne.s .flXLst
moveq #1,d0 ; Fetching.
bsr FetchToA ; Compile a fetch of the XL locn to A1
bra .fl2
.flXLst moveq #2,d0 ; Storing.
bsr FetchToD ; Compile fetch of XL locn to D2
bra .flst1 ; Now handle as for operand in Dn
; ==============================
; METHOD SUPPORT
; ==============================
loc
hGenAddr
; ( base-reg displ ind# -- ) Called when we are compiling an in-line
; method, and generating the object address. The "base-reg" may
; be negative, in which case the "displ" is an absolute address.
loc
bsr saveOD
move.b #mdBD,opMode
pop.l d0
move.b D0,opind
pop.l d0
pop.l d1
bpl.s .bd
move.l d0,opAddr
bsr.s getBase ; If necessary, convert to base-displ
.bd move.l d0,opDispl
bset #6,d1
move.b d1,opBreg
bra FtchVal1
hLoadBA
; ( base-reg displ ind# -- ) Loads the base address of an object to A0.
; This is done for an ivar bind, in the case where we can't generate
; the ivar's address directly at compile time. This happens when the
; obj addr is in an object pointer.
BSR saveOD
MOVE.B #mdBD,opMode
POP.L D0
MOVE.B D0,opind
POP.L D0
MOVE.L D0,opDispl
POP.L D0
bset #6,d0
MOVE.B D0,opBreg
MOVEQ #0,D0
BRA FetchToA
Tempind long
TempLocDispl long
ixShift word
ClFlags word
iwPwr2 byte
align
hGenxAddr
; ( xwid xoffs base-reg displ local-displ ind flags -- )
; Called by IX when we are compiling an in-line method, and generating
; the address of an indexed element of the current object.
; The base-reg, displ and ind refers to the obj addr. xoffs is the offset
; to the indexed area, if we know it. This will happen if the obj
; is a straight object or an ivar (ivars are generic to a class, but
; each one has a fixed xoffs). In these cases we can absorb the xoffs
; at compile time. If, however, the "obj" is self or super, then we won't
; know the xoffs at compile time, since at different points in the class
; hierarchy the xoffs is different. It is always located at run time
; in the word preceding the class pointer. In this case we will pass in
; a negative "xoffs".
; As for hGenaddr, the "base-reg" may be negative, which means that the
; "displ" is actually an absolute addr.
loc
bsr saveOD
pop.l D0
move.w D0,ClFlags-hbase(A4) ; Save class flags in ClFlags
pop.l tempind-hbase(A4) ; Save ind in tempind
pop.l tempLocDispl-hbase(A4) ; And local displ in tempLocDispl
pop.l d0 ; Displ to D0
pop.l d1 ; Base reg to D1
bpl.s .bd
bsr getBase ; If necessary, convert to base-displ
.bd move.l d0,d7 ; D7 = displ
move.l d1,d6 ; D6 = base reg
pop.l d5 ; D5 = xoffs
pop.l d4 ; D4 = xwid
MOVEQ #0,D0
BSR GetToReg ; Make sure the index is in Dn
PUSH.L D0 ; - leaves actual reg no in D0 - save it
MOVE.L D4,D2
MOVEQ #0,D3 ; Set up to find if only one bit is set.
.loop ROR.L #1,D2 ; Rotate width right - low bit goes to carry
BCS.S .gx0 ; as well as to high word (which we'll ignore)
ADDQ #1,D3
BRA.S .loop
.gx0 TST.W D2 ; Any other bits of the width set? Note this
SEQ iwPwr2-hbase(A4) ; is a word test, ignoring the high garbage
BNE.S .chkD0 ; No
MOVE.W D3,ixShift-hbase(A4) ; Yes - i.e. a power of 2.
; Is it 2**0 = 1 ?
BEQ.S .gx1 ; Yes - we can leave index where it is
.chkD0 CMP.B #2,D0 ; No - index must go to a temp Dn
BLE.S .gx1
OR.W #$2000,D0
PUSH.L D0
JSR wcomma ; Emit MOVE to get it to D0 if necessary
CLR.L (A6) ; Reset our index reg # to D0
.gx1 CLR.W OD-hbase(A4) ; Can't opt back
BSR initODs ; Now get a new desc ready to address the obj
MOVE.B #AnReg,opToFrom
MOVE.B #otFetch,(A0)
MOVE.B #mdBD,opMode
MOVE.L D7,opDispl
bset #6,d6
MOVE.B D6,opBreg
MOVE.B tempind+3,opind ; Is this the actual obj addr?
BEQ.S .gx2 ; Yes
BSR CompFetch ; No: compile a load of the addr to A0
move.b #AnReg,opBreg ; A0 is now the base reg
clr.l opDispl ; and the displ is zero
.gx2 move.l tempLocDispl,d0
bsr offsetAddr ; Offset the obj addr by the local offset
tst.w d5 ; Do we have a real xoffs?
bmi.s .gxNox
move.w d5,d0 ; Yes - add the xoffs to the obj addr
ext.l d0 ; to get the addr of the indexed area
bsr offsetAddr
bra.s .doChk
.gxNox MOVEQ #0,D0 ; No. First compile a LEA to A0
BSR compLEA
PUSH.L xgxself ; then the sequence to get the index base
JSR comma ; to A0
PUSH.L xgxself+4
JSR comma
move.b #AnReg,opBreg
CLR.L opDispl
.doChk btst.b #0,ClFlags+1 ; Is this array large ( >64K elements )?
bne.s .chkDone ; Yes: skip CHK
moveq #-2,d0
bsr OffsetAddr ; Range word is at -2 rel to index base
moveq #0,d0
bsr LoadBase ; LoadBase for CHK or LEA
move.l opDispl,d0
addq.l #2,d0 ; Will displ of index base fit in 8 bits?
bsr ByteChk
beq.s .dc3 ; Yes - no need to LEA
moveq #0,d0 ; No
bsr CompLEA ; LEA <range word>,A0
move.b #mdBD,opMode ; Now mode = BD
move.b #AnReg,opBreg ; Breg = A0
clr.l opDispl ; Displ = 0
.dc3 MOVE.L (A6),D0 ; Index D reg no to D0
ROR.W #7,D0
OR.W #$4180,D0
BSR CompMop2 ; Compile CHK
moveq #2,d0
bsr OffsetAddr ; Restore index base addr
resetDP ; We can't optimize back from here
.chkDone
MOVE.L (A6),D0 ; Restore D0 = index D reg no
MOVE.B #mdX,opMode
MOVE.B D0,opXreg
CLR.B opind
TST.B iwPwr2-hbase(A4) ; Index width = power of 2?
BEQ.S .doMul ; No
MOVE.W ixShift,D0 ; Yes
BNE.S .shft
BSR CompAnyNew ; ..and if 2**0, no shift necessary
ADDQ #4,A6
RTS
.shft ROR.W #7,D0
OR.L (A6)+,D0
OR.W #$E188,D0 ; LSL.L #n,Dn. Actually it will always
PUSH.L D0 ; be D0 unless we change something later.
JSR wcomma
.mkdp resetDP ; If we shifted or MULU'd, we can't opt back
BRA CompAnyNew
.doMul btst.b #0,ClFlags+1 ; Is this array large ( >64K elements )?
bne.s .domulx
or.l #$C0FC0000,d4 ; mulu #n,dn
pop.l d0
ror.l #7,d0
or.l d0,d4
push.l d4
.comma jsr comma
bra.s .mkdp
.domulx move.l d4,d1 ; We have a large array.
moveq #1,d0 ; Compile MOVEQ of the elt width to D1.
bsr CompMOVEQ
addq #4,a6
get.L xMulX,-(a6) ; And compile call to MulX to multiply it
; by the (long) index in D0. NOTE: this
; ASSUMES that the index is in D0 (it is,
; unless we change something later) and that
; the width is < 256. Also the MulX code
; ASSUMES that the top half of D1 will be zero
; if we're running on a 68020 or better
; - it uses a MULU.L instruction.
bra.s .comma
; hEB ( cfa -- )
; Compiles an early bind. Called from EB. Code will just have been compiled
; to get the object's address to the stack at run time. Our optimization
; improves this if possible.
heb bsr saveOD ; We compile:
moveq #AnReg,d0 ; (get ^obj to A0, by LEA or whatever)
bsr GetToReg
heb1 get.l HeldMod,d0 ; Are we invoking a module?
bne.s .hebMod ; Yes
; No. Just compile
bra CompJSR ; JSR cfa (the method)
.hebMod move.l d0,(a6) ; Replace "cfa" (garbage) with active
lea tmpOD,a0 ; module addr
bsr setAddr
moveq #0,d0
bsr LoadBase
moveq #1,d0
bsr CompLEA ; lea <^mod>,a1
push.l EBmod
bsr CompJSR ; jsr EBmod
get.l MethIndex,-(a6)
jmp wcomma
hStkObj
; ( -- base displ ind ) Sets up for an early bind to an object whose
; (data) addr is on the stack at run time. We also handle object
; pointers this way, by first compiling a fetch of the objPtr
; to the stack, and relying on our optimization to improve the code.
; Rather than leaving the ^obj on the stack, we return the addressing
; info back to the CLASS code. This is because we may be binding to an
; inline method which uses OBJ anywhere - more than once, even.
loc
BSR saveOD
LEA ODsav,A0
CMP.B #otFetch,OD-hbase(A4)
BNE.S .getToA0
CMP.B #mdBD,opMode
BNE.S .getToA0
MOVEQ #0,D0
MOVE.B opBreg,D0
and.b #7,d0
PUSH.L D0
MOVE.L opDispl,D0
PUSH.L D0
MOVEQ #0,D0
MOVE.B opind,D0
PUSH.L D0
backDP
BSR popOD
RTS
.getToA0
MOVEQ #AnReg,D0
BSR GetToReg
CLR.L -(A6)
CLR.L -(A6)
CLR.L -(A6)
RTS
; =========================
; DO LOOPS
; =========================
loc
; CompPlLoop handles +LOOP. We optimize the case <fetch> +LOOP.
PlLoop_M macrox &1
POP.L D0
BPL.S .up
ADD.L D0,D3
MOVE.L (A7),D0
SUBQ.L #1,D0
CMP.L D3,D0
BRA.S .tst
.up ADD.L D0,D3
CMP.L (A7),D3
.tst BLT &1
ADDQ.L #8,A7
MOVE.L (A7)+,D3
endm
PlLpD_M macrox &1
BPL.S .up
ADD.L D0,D3
MOVE.L (A7),D0
SUBQ.L #1,D0
CMP.L D3,D0
BRA.S .tst
.up ADD.L D0,D3
CMP.L (A7),D3
.tst BLT &1
ADDQ.L #8,A7
MOVE.L (A7)+,D3
endm
plLpUp_m macrox &1
CMP.L (A7),D3
BLT &1
ADDQ.L #8,A7
MOVE.L (A7)+,D3
endm
PlLpDn_m macrox &1
CMP.L (A7),D3
BGE &1
ADDQ.L #8,A7
MOVE.L (A7)+,D3
endm
pplloop_m macrox
plLoop_m dummylab
endm
ppLpD_m macrox
PlLpD_m dummylab
endm
ppLpUp_m macrox
plLpup_m dummylab
endm
ppLpDn_m macrox
plLpDn_m dummylab
endm
loc
nohead pplloop,inline
loc
nohead ppLpD,inline
loc
nohead ppLpUp,inline
loc
nohead ppLpDn,inline
loc
word
dummyLab
dc.w $FFE2
CompPlLoop
BSR SaveOD
BSR ChkOpt
BEQ.S .cplNo
CMP.B #otFetch,D1
BEQ.S .cplF
.cplNo compyl pplloop
RTS
.cplF lea ODsav,A0
backDP
cmp.b #mdLit,opMode
beq.s .cplLit
st ForceToR-hbase(a4)
moveq #0,D0
bsr FetchToD
compyl ppLpD
rts
.cplLit move.b #otAdd,operation-hbase(A4)
LEA ODnew,A1
MOVE.B #3,opReg(A1)
MOVE.B #mdDn,opMode(A1)
MOVE.B #1,opInd(A1) ; Contents of the reg is the operand
BSR OP2
LEA ODsav,A0
TST.L opLit
BLT.S .cplDn
compyl ppLpUp
RTS
.cplDn compyl ppLpDn
RTS
; =============================
; COMPIMP ( ^mod -- ) handles the compilation of the runtime code
; for imported words, as defined in the construct
; FROM <modName> IMPORT{ <name0> <name1> ... }
; ^mod is the data address of the module object, and n is the number
; of this name in the list, starting at zero.
Compimp
geta modEntry,-(A6) ; We compile:
bsr CompJSR ; JSR modEntry
clr.l -(a6)
jsr wcomma ; Leave space for 2-byte index into the
; module's export table.
pop.l d0
get.l DP,d1
sub.l d1,d0
push.l d0
jmp wcomma ; module offset (2 bytes)
; ============================================
; HANDLERS FOR INDIVIDUAL TYPES AND OPERATIONS
; ============================================
loc
constAddr long
hndlr const_h,0 ; const_h
MOVE.L (A6),A0
MOVE.L A0,constAddr-hbase(A4)
MOVE.L (A0),(A6) ; Fetch const value
litCon BSR SaveOD
MOVE.W #tsFetch+Lcode,(A0) ; Type = fetch, length = L
MOVE.B #stkPush,opToFrom ; Mark dest as stk
POP.L D0 ; Value
MOVE.L D0,opLit ; Store in opLit field in descriptor
BSR ByteChk
beq.s .lit
; BNE.S .long
; MOVE.B #1,opShort ; Set short lit flag
; BRA.S .lit
.long TST.L constAddr-hbase(A4)
BEQ.S .lit
PUSH.L constAddr-hbase(A4) ; Long constant - same as Value.
BSR SetAddr
BRA.S .compF
.lit MOVE.B #mdLit,opMode ; Literal number. Set mode = lit
clr.b opind
.compF BSR compFetch
.mkopt BSR PushOD
RTS
Literal
; ( n -- )
CLR.L constAddr-hbase(A4)
BRA.S litCon
LitAddr
; ( addr -- )
litAddr1 ; create_h comes in here
bsr saveOD
bsr SetAddr
clr.b opind
bra.s FtchVal1
; ===========================
hndlr val_h,0 ; val_h
valH BSR SaveOD
bsr SetAddr
TST.L opcode-hbase(A4)
BNE.S StoreVal
; BSR SetAddr ; Fetch
ftchVal1
MOVE.W #tsFetch+Lcode,(A0) ; Type/subtype = fetch, long
MOVE.B #stkPush,opToFrom ; Dest = stack
bra fChk
StoreVal ; Store
; bsr setAddr
stVal1 move.l opcode,d3
lsl.w #8,d3
btst #flFP,opFlags ; Is it an FP value?
bne.s .stv2
.stv1 or.b #Lcode,D3
.stv2 move.w d3,(a0) ; Set type/subtype (in ODnew)
move.b #stkPop,opToFrom ; Source = stack
bra stchk
objPtr_h equ val_h
; ===========================
hndlr vect_h,0 ; vect_h
tst.l opcode-hbase(A4)
beq CompJSR ; Note - at the moment, any
; code other than zero is assumed
; to be a store to the vector. It really
; doesn't make much sense to add etc.!
bsr saveOD
addq.l #4,(a6) ; Dest addr - skip the JSR ExVect
bsr SetAddr
moveq #0,D0
bsr loadbase
moveq #0,d0
bsr CompLEA
.toVect get.L xjsrToVect,-(A6)
JMP comma
; hDoEx handles the execution of x-array elements. The preceding code
; should have pushed the required element address at run time.
loc
hDoEx bsr saveOD
bsr ChkOpt
beq.s .noOpt
lea ODsav,A0
backDP
; move.b #1,opind
moveq #0,d0
bsr FetchToA
.ex1 get.l xAtAbs,-(a6)
jsr comma
compop xJsrA0
rts
.noOpt compop xPopA0 ; This will only happen if optimization
bra.s .ex1 ; is disabled.
; ========================
; Handler for floating fetch and store. These ops are intended for optimized
; access to floating arrays.
hndlr Fat_h,0 ; Fat_h
sf Flocq-hbase(a4)
st FatStq-hbase(a4)
addq #4,a6
bsr saveOD
move.b #otFetch,(a0)
move.b #stkPush,opToFrom ; Dest = stack
move.b #mdBD,opMode ; Mode = base-displacement
move.b #stkPop,opBreg ; Base reg = stack
move.b #1,opind ; Memory operand (displ = 0)
get.l UseFPUq,d0 ; Compiling FPU code?
beq FvNoOpt ; No. Proceed as for floating values
bset #flFP,opFlags
bra atChkOpt
hndlr Fst_h,0 ; Fst_h
sf Flocq-hbase(a4)
st FatStq-hbase(a4)
addq #4,a6
bsr saveOD
move.b #otStore,(a0)
move.w #otStore,opcode+2-hbase(a4)
move.b #stkPop,opToFrom ; Source = stack
move.b #mdBD,opMode ; Mode = base-displacement
move.b #stkPop,opBreg ; Base reg = stack
move.b #1,opind ; Memory operand (displ = 0)
get.l UseFPUq,d0 ; Compiling FPU code?
beq.s FvNoOpt ; No. Proceed as for floating values
move.b #fbFP,opFlags ; Yes. Set FP flag in desc
bra stChkOpt ; Then proceed as for !
; Handler for floating values and constants
hndlr Fval_h,0 ; Fval_h
fvalcon sf Flocq-hbase(a4)
sf FatStq-hbase(a4)
bsr saveOD
addq.l #2,(a6) ; Offset addr by 2 to skip status word
bsr SetAddr ; Set up ODnew
; Now, if we're compiling FPU code and if we're storing or operating, we go through
; the normal store mechanism to allow optimization.
fval1 get.l UseFPUq,d0
beq.s FvNoOpt
tst.w opcode+2-hbase(a4)
beq.s FvNoOpt
move.b #fbFP,opFlags
bra StVal1
FvNoOpt clr.b opind
move.b #AnReg+1,opToFrom ; Compile:
bsr CompFetch ; LEA <addr>,A1 (or whatever)
tst.w opcode+2-hbase(a4)
bne.s .fvst ; Then, if fetching:
fvLF get.l ptrLfloat,-(a6) ; JSR Lfloat-base(a3)
tst.b FatStq-hbase(a4) ; If this is F@ or F!, we bypass the status
beq.s .fvJSR ; word check, since there isn't one! The
addq.l #8,(a6) ; check is 8 bytes long, hopefully.
.fvJSR bsr CompJSRnoPush
get.l UseFPUq,d0 ; Compiling FPU code?
beq.s .fvOut ; No: finished. Don't push descriptor.
lea ODnew,a0 ; Yes
move.b #otFetch,(a0)
move.b #1,opind
move.b #fbFP,opFlags ; Set FP bit in flags
bra PushOD
.fvOut rts
; If storing, we handle much as
.fvst get.l ptrToFval,-(a6) ; for flt locals.
bra flst2
Fcon_h equ Fval_h ; Fcon_h
hndlr FCRcon_h,0 ; FCRcon_h
get.l useFPUq,d0
bne.s .fcrFPU
addq.l #2,(a6)
bra Fvalcon
.fcrFPU pop.l a0
move.w (a0),d0 ; ROM offset to D0
sf Flocq-hbase(a4)
sf FatStq-hbase(a4)
bsr saveOD
move.b #mdFPn,opMode ; We'll load the ROM constant into FP0
move.b #otFetch,(a0)
move.b #1,opind
move.b #fbFP+fbFCR,opFlags ; Set FP and FCR bits in flags
move.b d0,opRoffs ; Set ROM offset byte
push.l a0
bsr newClrOD
move.b #StkPush,opMode
move.l a0,a1
move.l (a6),a0
bsr FPmove
bsr releaseOD
pop.l a0
bra PushOD
; hCompFPUL handles the compilation of a literal floating value, if we're
; compiling FPU code. Code has already been compiled to put the addr of the
; floating quantity into A1; here we set the operand address as A1 indirect,
; then continue as for floating values.
svFPlit long 3 ; Save area for current FP literal
prevFPlit long 3 ; Saves previous FP literal, so that if we
; recompile while optimizing, we can still
; get the value.
hCompFPUL
movem.l svFPlit,d0-d2
movem.l d0-d2,prevFPlit-hbase(a4)
pop.l svFPlit-hbase(a4)
pop.l svFPlit+4-hbase(a4)
pop.l svFPlit+8-hbase(a4)
sf Flocq-hbase(a4)
sf FatStq-hbase(a4)
bsr saveOD
move.b #mdBD,opMode
move.b #AnReg+1,opBreg
move.b #otFetch,(a0)
move.b #1,opind
move.b #fbFP+fbLit,opFlags ; Set FP and Literal bit in flags
push.l a0
bsr newClrOD
move.b #StkPush,opMode
move.l a0,a1
move.l (a6),a0
bsr FPmove
bsr releaseOD
pop.l a0
bra PushOD
hndlr reg_h,2 ; reg_h
addq.l #4,(a6) ; Skip xinfo flag bytes
BSR SaveOD
POP.L A1
MOVE.B (A1)+,opMode
MOVE.B (A1)+,opReg
MOVE.B #Lcode,opSize
CMP.B #mdAn,opMode ; Dn or An?
BEQ.S .An
BRA.S loc2
.An tst.w opcode+2-hbase(A4) ; An. Are we fetching?
bne.s loc3 ; No
move.b #mdBD,opMode ; Yes: change to addr, BD mode, zero displ,
bset #6,opBreg ; with that reg as the base. This gives
clr.b opind ; better optimization opportunities.
bra.s loc3 ; NOTE that opReg and opBreg are the same
; byte.
hndlr col_h,0 ; col_h
.col BRA compJSR
call_h equ col_h
class_h equ col_h ; No difference here, but the different
class_in_mod_h equ col_h ; handler code is needed in various places.
imported_h equ col_h
; ========================
hndlr create_h,0 ; create_h
BRA litAddr1
hndlr builds_h,4 ; builds_h
addq.l #4,(a6) ; As for Create, but there's
bra litAddr1 ; an extra 4 bytes before
; the data field.
; ========================
hndlr obj_h,0 ; obj_h
ADDQ.L #8,(A6)
BRA litAddr1
; ========================
hndlr PushDesc_h,0 ; PushDesc_h
bsr saveOD ; Called from main compiler if we need
pop.l a1 ; to push a descriptor whose top 2 bytes
lea ODnew,a0 ; are given as xinfo.
addq.l #4,a1 ; Skip xinfo flag bytes
move.w (a1)+,(a0) ; Move type & subtype bytes to new desc
move.w (a1)+,d0 ; Get "real" handler code
push.l a1 ; Push "real" cfa
push.w d0
bsr pushOD
pop.w d0
bpl.s .inline
neg.w d0
lea htable,a0
move.w 0(a0,d0.w),d0
jmp 0(a4,d0.w)
.inline move d0,-(a6)
clr -(a6)
jmp ncomma
; DOES> words have a relocatable addr at the cfa, pointing to the run-time
; code to be executed. The data starts at the cfa+4. A call to a DOES> word
; compiles a LEA of the data addr to A0, followed by a JSR to the run-time code.
; At the beginning of the run-time code we compile a push of A0, so we have the
; data addr on the stack, as required.
hndlr does_h,4 ; does_h
; ( cfa -- )
move.l (a6),-(a6)
addq.l #4,(a6)
move.w #$41C0,d0
bsr OpAndAddr ; LEA <cfa+4>,A0
move.l (a6),a0
jsr doPAtAbs
move.l a0,(a6)
bra compJSR ; JSR <run-time code>
FixDoes
bsr initODs
move.b #mdBD,opMode
move.b #AnReg,opBreg
clr.b opind
bra FtchVal1
; ========================
; SWAP_H handles SWAP. We optimize the case where the swap is preceded
; by two fetches. This may not seem likely to occur, but it actually
; can occur quite readily with inline definitions.
hndlr swap_h,4 ; swap_h
addq.l #4,(a6) ; Skip xinfo flag bytes
bsr saveOD
jsr length
pop.l d4 ; Save descriptor code in D4
lea ODsav,a0 ; Look at previous op
cmp.b #otFetch,(a0) ; Fetch?
bne.s .no ; No: don't optimize
move.l a0,a1 ; Yes: save desc addr in A1
downOD ; Look at op before that
cmp.b #otFetch,(a0) ; Fetch?
bne.s .no ; No: don't opt
tst.b opBreg
bmi.s .no ; Yes, but base or index regs are stack, so
tst.b opXreg ; don't opt.
bmi.s .no ; Note, if this had happened with the other
; desc, this desc here would have been
; absorbed. So then it wouldn't have been
; here at all! So we didn't need to test
; there.
addq #4,a6 ; All OK: we'll optimize. Drop SWAP cfa
backDP
bsr exgOD ; Swap the descriptors
MarkDP
bsr CompFetch ; And compile them in reverse order. This
upOD ; has the same effect as the SWAP, for free
MarkDP
bsr CompFetch
ODvalid ; OD is valid
rts
.no pop.l a0 ; No optimization. Get inline code addr
push.l (a0)+
jsr comma ; And compile the 8 bytes.
push.l (a0)+
jsr comma
lea ODnew,a0
move.w d4,(a0)
bra pushOD ; Push "swap" descriptor
; ========================
; PM_H is the handler for these operations: + - and or xor.
loc
pmOptFlg byte
pmRevFlg byte
pmChnFlg byte
revFlg byte
align
hndlr pm_h,0 ; pm_h
addq.l #4,(a6) ; Skip xinfo flag bytes
st pmOptFlg-hbase(A4)
sf pmRevFlg-hbase(A4)
move.b #stk,pmChnFlg-hbase(A4) ; Means no chaining yet
pop.l a0 ; Look at xinfo stuff
move.b 1(a0),operation-hbase(a4)
move.b opShiftCnt,shiftCnt-hbase(a4)
bsr SaveOD
move.b #stk,opMode ; Assume dst operand is stk, for now
pmSetupDone
.pmchk bsr ChkOpt
beq .noOpt
lea ODsav,A0
cmp.b #otFetch,(a0)
beq .pmF
cmp.b #otSWAP,d1
beq .pmSwap
cmp.b #otOVER,d1
beq .pmOver
cmp.b #otPMops,d1
blt .noOpt
cmp.b #otPMend,d1
bge .noOpt
; Previous op is another pm-type op. We'll chain them, by recompiling
; the first op to dest Dn (usually D1), then compiling the second op with
; Dn as src and stk as dest.
move.b operation,d2
swap d2
move.b shiftCnt,d2
push.l d2 ; Save Operation and ShiftCnt
moveq #1,d0
bsr op2Reg ; Recompile first op to Dn
move.b d0,pmChnFlg-hbase(A4) ; Indicates chaining on Dn
pop.l d2 ; Restore Operation & ShiftCnt
move.b d2,shiftCnt-hbase(a4)
swap d2
move.b D2,operation-hbase(A4)
lea ODnew,a0
markdp
move.b #mdDn,opMode ; Set up desc for Dn in ODnew
move.b d0,opReg
cmp.b #otMon,D2
bge.s .pmchnMon ; If this op is a monadic
cmp.b #otSUB,D2
bne.s .pmchn1
tst.b pmRevFlg-hbase(A4)
bne.s .pmchnR
.pmchn1 BSR newClrOD
MOVE.B #stk,opMode
MOVE.L A0,A1
LEA ODnew,A0
BSR OP2
BSR releaseOD
BRA .pmPsh
.pmchnR compop xSubD1
.pmMv compop xmvD1stk
BRA .pmPsh
.pmchnMon ; It's a monadic operation.
move.l a0,a1 ; D1 is operand (ODnew)
bsr OP2
compop xPushD1 ; Compile PUSH.L D1
bra .pmPsh
; Prev op is a fetch.
.pmF cmp.b #otMon,operation-hbase(A4)
bge .noOpt ; If a monadic op, can't opt on a fetch
BackDP ; Absorb fetch
cmp.b #mdLit,opMode
beq.s .pmLit ; If a literal
cmp.b #mdX,opMode
bhi.s .pm1
tst.b opind
beq .pmAd2 ; If it's an addr fetch
bra.s .pm1
.pmLit ; Literal. All normal optimization will be handled by OP2,
; so here we only check for zero. Yes, this can happen! Probably
; the most common situation will be when we are adding base offsets to
; an object's address when generating the binding code. These offsets
; will often be zero. The operation is always addition, but it's
; just as easy to include subtraction and OR here as well.
; In these cases, we just leave the DP backed, pop the descriptors
; to where they were before the literal zero, and get out without
; compiling anything. We could also do something about AND,
; but I doubt it's worth it.
TST.L opLit
BNE.S .pm1
cmp.b #otAND,operation-hbase(A4)
BEQ.S .pm1
BSR popOD
RTS
; We have (non-addr)-fetch, pm-op.
.pm1 downOD ; Look at previous descriptor
; - we may be able to further optimize
cmp.b #otFetch,(a0)
beq .pmFF ; If another long fetch
cmp.b #otPMops,(a0)
blt .pmUp
cmp.b #otPMend,(a0)
bge .pmUp ; If not another pm-type op, we can't do
; anything more
; We have pm-op, fetch, pm-op. We recompile the first pm-op to Dn, then the second
; to operate between the addressed location and Dn. Then we compile a push of Dn
; to the stack. If we next encounter another pm-op, we'll continue the chain,
; deleting the push.
move.b operation,d0
swap d0
move.b shiftCnt,d0
push.l d0
moveq #1,d0
bsr op2reg
pop.l d1
move.b d1,shiftCnt-hbase(a4)
swap d1
move.b d1,operation-hbase(A4)
upOD
.pmMark markDP
move.b d0,d1
or.b #fchChn,d1
move.b d1,pmChnFlg-hbase(A4) ; Mark as chained with fetched operand
LEA ODnew,A1
MOVE.B #mdDn,opMode(A1)
move.b d0,opReg(a1)
BSR newOD
MOVEQ #1,D0
BSR LoadBase
BSR OP2
BSR releaseOD
TST.B pmRevFlg-hbase(A4)
BEQ.S .pmToStk
compop xNegD1 ; We hope it was really D1!!
.pmToStk
compop xPushD1
BRA .pmPsh
; We have (non-addr)-fetch, fetch, pm-op. Note, the second fetch CAN be
; an addr fetch if the pm-op isn't add.
.pmFF cmp.b #mdX,opMode
bhi.s .pmFF1
tst.b opind
beq .pmAd ; if 1st operand is an addr fetch
; Here we optimize on two preceding fetches -- e.g. val1 val2 + is most
; efficiently compiled to
;
; move.l val1,d1
; add.l val2,d1
; move.l d1,-(a6)
;
; but we also check for the fetches both being literal, as in this case we can
; do the op now at compile time.
.pmFF1 backDP
cmp.b #mdLit,opMode
bne.s .pmFF2
move.l opLit,d2
upOD
cmp.b #mdLit,opMode
beq .pmLL
downOD
.pmFF2 moveq #1,d0
st ForceToR-hbase(a4) ; Force temp D1 to be used
bsr FetchToD
UpOD
bra.s .pmMark
.pmUp upOD ; We come here from a few other places too
BRA.S .pmOP2
.noOpt UseODsrc ; Can't optimize
MOVE.B #stkPop,opMode
MOVE.B #Lcode,opSize
.pmOP2 LEA ODnew,A1
BSR newOD
MOVEQ #1,D0
BSR LoadBase
BSR OP2 ; Compile operation
TST.B pmRevFlg-hbase(A4) ; Was it a reversed operation?
BEQ.S .pmTst
MOVE.B #mdDn,opMode ; Yes - the result will be in Dn
MOVE.B opToFrom,opReg ; where n will have been left in
; opToFrom by OP2.
BSR CompMove ; This moves it to the stack (A1 desc).
.pmTst BSR ReleaseOD
TST.B pmOptFlg-hbase(A4)
BNE.S .pmPsh
RTS
.pmPsh LEA ODnew,A0
TST.B pmRevFlg-hbase(A4)
BNE.S .optRSub
move.b operation,D0
BRA.S .pmPsh1
.optRSub
MOVE.B #otRevSub,D0
.pmPsh1 MOVE.B D0,(A0) ; Set up the descriptor to push
moveq #0,d0
move.b shiftCnt,d0
move.b d0,opShiftCnt
MOVE.B pmChnFlg,opToFrom
BRA pushOD
; Optimization of arithmetic on an address.
;
; We handle address optimizations in two places. First, if we get an address
; fetch (that is a fetch with opind zero), followed or by some arithmetic, we
; detect it here. We attempt to absorb the arithmetic at compile time, or
; if we can't do that, but the operation is add, we see if we can use index
; mode or at least LEA the addr to An then add into An. In the latter 2
; cases we generate a new descriptor referencing An and block optimizing
; further back. This is all on the assumption that we're eventually going
; to want the result in An -- not unreasonable.
;
; The other place where we try to optimize an address is if we didn't
; get an address fetch but we get @ or ! preceded by a + or -. In this
; case we look for a preceding literal which can be absorbed into the
; address. We do this at OptAddr later.
.pmAd ; We have an address fetch followed by
; another fetch.
upOD
cmp.b #mdDn,opMode ; Is following fetch a D reg?
bne.s .pma1
cmp.b #1,opind
beq.s .pmAX ; Yes - maybe generate index mode
.pma1 cmp.b #otSUB,operation-hbase(A4) ; Is op add or subtract?
bgt.s .pmOP2 ; No - don't optimize
cmp.b #mdLit,opMode ; Is following fetch a literal?
bne.s .pmOP2 ; No - don't optimize here
; (but may do it later)
; We have addr fetch, literal, add/sub. We absorb the literal add or sub into
; the address at compile time.
sf pmOptFlg-hbase(a4) ; We're optimizing now, so don't do it again
move.l opLit,d1
downOD
move.l opDispl,d0
; ext.l d0
cmp.b #otSUB,operation-hbase(a4)
beq.s .pmaSub
add.l d1,d0
bra.s .pma2
.pmaSub sub.l d1,d0
.pma2 upOD
BSR popOD ; Fix descriptors - absorb 2nd fetch
LEA ODsav,A0
BackDP
move.l d0,opDispl
bra.s .pmCF
; We have addr fetch, D reg fetch, add/sub. If it's an add, we can generate
; an index mode.
.pmAX
cmp.b #otADD,operation-hbase(A4)
BNE .pmOP2 ; We can only convert to index if it's add
sf pmOptFlg-hbase(a4) ; We're optimizing now, so don't do it again
MOVE.B opReg,D3
BSR popOD ; Fix descriptors - absorb reg fetch
LEA ODsav,A0
BackDP
MOVE.B #mdX,opMode
MOVE.B D3,opXreg
.pmCF BSR CompFetch ; Now recompile the address fetch
ODvalid
RTS ; OD is valid, and we're done.
; 2nd operand is an address fetch.
.pmAd2 cmp.b #otADD,operation-hbase(a4) ; We can only do addr optimization if
bne .pm1 ; the op is add. Otherwise just treat
; as an ordinary fetch.
downOD
cmp.b #otFetch,(a0)
beq.s .pmFad2 ; If previous desc is a fetch
sf pmOptFlg-hbase(a4) ; We're optimizing now, so don't do it again
cmp.b #otPMops,(a0)
blt .pmNad2
cmp.b #otPMend,(a0)
bge .pmNad2 ; If not recognizable
; We have an integer arith op followed by an addr fetch to be added.
moveq #1,d0
bsr op2Reg ; Recompile arith op to D1
lea ODsav,a0
moveq #0,d0 ; Compile:
bsr LoadBase
get.l DP,d0
moveq #0,d0
bsr CompLEA ; lea <addr>,a0
get.l DP,d0
compop xAddD1A0 ; add.l d1,a0
lea ODsav,a0
.A0ind move.b #mdBD,opMode ; Change descriptor to A0 indirect
move.b #AnReg,opBreg ; - actually BD mode, A0 base, zero displ.
clr.l opDispl
resetDP ; Can't opt further back now, since we
; just compiled special code
bra .pmCF ; Recompile the address fetch using the
; modified descriptor, and out.
; We have a fetch followed by an addr fetch to be added.
.pmFad2 backDP ; We recompile in the reverse order
move.l a0,a1 ; so the .pmAd code above can handle it.
upOD ; Note that we mightn't necessarily end up
bsr ExgOD ; optimizing, so we leave pmOptFlg alone
downOD ; for now.
get.L DP,opDP
bsr CompFetch
bra .pmAd
; We have nothing recognizable followed by an address fetch to be added.
; We just add whatever is on the top of the stack at run time.
.pmNad2 lea ODsav,a0
moveq #0,d0 ; Compile:
bsr LoadBase
get.l DP,d0
moveq #0,d0
bsr CompLEA ; lea <addr>,a0
get.l DP,d0
compop xAddStkA0 ; add.l (a6)+,a0
bra .A0ind ; Change desc to A0 indirect, etc.
; Previous op was SWAP. We absorb it.
.pmSwap cmp.b #otSUB,operation-hbase(A4)
BNE.S .notSub
EOR.W #$100,RevOpnds-hbase(A4)
SNE pmRevFlg-hbase(A4)
.notSub backDP
LEA ODnew,A0
resetDP
BSR dropOD
BRA .pmchk
; Previous op was OVER. We compile a MOVE.L 4(A6),D0
; then call OP2 with the 2nd operand in D0 at run time.
.pmOver backDP
compopl xMv2ndD0
lea ODreg,a0
move.b #mdDn,opMode
move.b #Lcode,opSize
clr.b opReg
bsr .pmOP2
NoOpt
rts
.pmLL move.l opLit,d1
downOD
backDP
move.l opLit,d0
moveq #0,d2
move.b operation,d2
lsl.w #1,d2
lea xadds-(otADD*2),a0
move.w 0(a0,d2.w),d2
or.w #$81,d2 ; Make it XXX.L d1,d0
move.w d2,.doit-hbase(a4) ; Store op for execution
bsr FlushCache
.doit _debugger
bsr popOD
lea ODsav,a0
move.l d0,opLit
bra CompFetch
; ===================
; MultDiv_h ( ^code -- ) handles * *W and /. The only special action we take
; is to check for two preceding literal fetches, so we can do the op now at
; compile time.
hndlr MultDiv_h,4 ; MultDiv_h
loc
addq.l #4,(a6) ; Skip xinfo flag bytes
move.l (a6),a0
move.b 1(a0),operation-hbase(a4) ; Get operation
bsr saveOD
bsr chkOpt
beq.s .noOpt
lea ODsav,a0
cmp.b #otFetch,(a0)
bne.s .noOpt
cmp.b #mdLit,opMode
bne.s .noOpt
move.l opLit,d2
downOD
cmp.b #otFetch,(a0)
bne.s .noOpt1
cmp.b #mdLit,opMode
bne.s .noOpt1
backDP
addq #4,a6
push.l a0
push.l opLit
push.l d2
cmp.b #otDIV,operation-hbase(a4)
beq.s .mdDiv
jsr star
.md1 pop.l d0
pop.l a0
move.l d0,opLit
bsr popOD
lea ODsav,a0
bra CompFetch
.mdDiv jsr slash
bra.s .md1
.noOpt1 upOD
.noOpt addq.l #2,(a6)
bra CompJSR
; FP2_h ( ^code -- ) handles dyadic floating-point ops. It is rather
; like pm_h but a lot simpler (well, a little bit simpler?) because we don't
; have to check for as many different possibilities.
hndlr FP2_h,4 ; FP2_h
loc
sf pmRevFlg-hbase(a4)
get.l useFPUq,d0 ; Are we compiling FPU code?
bne.s .fpFPU
addq.l #2,(a6) ; No - skip the opcode and just compile
bra CompJSR ; a call to what follows there.
.fpFPU clr.b FPA-hbase(a4)
pop.l a0
move.b 1(a0),operation-hbase(a4)
move.b #stk,pmChnFlg-hbase(A4)
bsr saveOD
move.b #stk,opMode ; Assume dst operand is stack, for now
.fpChk bsr chkOpt
lea ODsav,a0
beq .noOpt
cmp.b #otFetch,d1
beq .fpF
; cmp.b #otSWAP,d1 ; NOTE: as yet, we're not handling
; optimization of SWAP with FP operations
; beq .fpSwap ; as it complicates things a lot,
; and probably isn't worth it.
cmp.b #otFPops,d1
blt .noOpt
cmp.b #otFPend,d1
bge .noOpt
; Preceding op was another FP op. Chain them.
fpChain
move.b operation,d0
push.w d0
moveq #1,d0
bsr FPop2reg ; Recompile preceding op to FP0 or FP1
move.b d0,pmChnFlg-hbase(a4)
pop.w d1
move.b d1,operation-hbase(a4)
lea ODnew,a0
markDP
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b d0,opReg
cmp.b #otFPmon,d1
bge.s .fpChnMon
; cmp.b #otFPnoncom,operation-hbase(a4)
; blt.s .fpchn1 ; At the moment we're not optimizing
; tst.b pmRevFlg-hbase(a4) ; FP operands over SWAP, so no reversed
; bne.s .fpChnR ; ops can occur.
.fpchn1 bsr newClrOD
move.b #stk,opMode
clr.b FPdispFlg-hbase(a4)
move.l a0,a1
lea ODnew,a0
clr.b FPA-hbase(a4)
bsr OP2
bsr releaseOD
bra fpPsh
;.fpChnR dc.w $FFE4
.fpChnMon
move.l a0,a1 ; Same FPn (ODnew) is both src and dst
bsr OP2
bsr newClrOD ; Compile a move of result to a new FP heap
move.b #stkPush,opMode ; block, and a push of the address.
exg a0,a1
bsr ToNewHeap
bsr releaseOD
bra fpPsh
; Preceding op was a fetch.
.fpF cmp.b #otFPmon,operation-hbase(a4)
bge .noOpt
backDP
btst #flLit,opFlags
sne d2 ; Remember in D2 if it was a floating literal
downOD ; What was op before that?
cmp.b #otFetch,(a0)
beq .fpFF ; Another fetch
cmp.b #otFPops,(a0)
blt .fpUp
cmp.b #otFPend,(a0)
bge .fpUp ; If not a FP op, we can't do anything more
; We have FP op, fetch, FP op.
move.b d2,usePrevFPlit-hbase(a4)
; If last fetch was an FP lit, and the following recompilation finds a
; literal fetch, it will be the previous value.
move.b operation,d0
push.w d0
moveq #1,d0
BSR FPop2Reg ; Recompile first FP op to FP0 or FP1
sf usePrevFPlit-hbase(a4)
pop.w d1 ; Restore everything
move.b d1,operation-hbase(A4)
upOD
.fpMark markDP
move.b d0,d6 ; Save reg# in D6
move.b d0,d1
or.b #fchChn,d1
move.b d1,pmChnFlg-hbase(A4) ; Mark as chained with fetched operand
lea ODnew,a1
move.b #mdFPn,opMode(a1)
move.b d0,opReg(a1)
move.b #fbFP,opFlags(a1)
bsr newOD
moveq #1,d0
bsr LoadBase
clr.b FPA-hbase(a4)
bsr OP2
bsr releaseOD
bsr compFPnew
move.b #$80,d1
tst.b d6
beq.s .fpF1
move.b #$40,d1
.fpF1 move.l #$F210F000,d0 ; fmovem fp0,(a0)
or.b d1,d0 ; Fix source reg#
push.l d0
jsr comma
compop xpushA0
bra fpPsh
; Here we optimize on two preceding fetches.
.fpFF btst #flFP,opFlags ; Was 1st one floating?
beq.s .fpUp ; No
backDP
move.b d2,usePrevFPlit-hbase(a4)
; If both fetches are literal, the following
; CompMoveToFPn call must use the earlier
; value. Maybe we can do this calculation
; at compile time later?
moveq #1,d0
clr.b FPA-hbase(a4)
bsr CompMoveToFPn
sf usePrevFPlit-hbase(a4)
UpOD
moveq #1,d0 ; It was FP1 we used
bra.s .fpMark
.fpUp upOD
bra.s .fpOP2
.noOpt UseODsrc ; Can't optimize
move.b #stkPop,opMode
move.b #1,FPdispFlg-hbase(a4) ; One operand to dispose
.fpOP2 lea ODnew,a1
bsr newOD
moveq #0,d0
bsr loadBase
clr.b FPA-hbase(a4)
bsr OP2
bsr releaseOD
fpPsh move.b operation,d0
lea ODnew,a0
move.b operation,(a0)
move.b pmChnFlg,opToFrom
move.b pmRevFlg,opSubType ; Will be NZ if op is reversed by SWAP
move.l FPDP,opFPDP
bra PushOD
; Previous op was SWAP. NOT YET!!!
;.fpSwap cmp.b #otFPnoncom,operation-hbase(A4)
; blt.s .comm
; eor.w #$100,RevOpnds-hbase(A4)
; sne pmRevFlg-hbase(A4)
;.comm backDP
; lea ODnew,A0
; resetDP
; bsr dropOD
; bra .fpChk
; ===================
; FP monadic ops. In the case of fabs and fnegate, these are so easy
; to do in main memory that we only use the FP regs if the operand is already
; there, or if it is going to be stored there. The latter case we can't
; determine now, so if an FP fetch or operation doesn't precede, we just
; push a descriptor and compile the default JSR. Then the store routine will
; see the descriptor and optimize if the destination is an FP location.
hndlr FP1_h,4
loc
move.l (a6),a0
move.b 1(a0),operation-hbase(a4)
get.l useFPUq,d0 ; Are we compiling FPU code?
bne.s .fpmFPU
.noOpt addq.l #2,(a6) ; No - skip the opcode and just compile
bsr CompJSR ; a call to what follows there.
bra fpPsh ; Push descriptor
.fpmFPU clr.b FPA-hbase(a4)
move.b #stk,pmChnFlg-hbase(A4)
bsr saveOD
move.b #stk,opMode ; Assume dst operand is stack, for now
.fpChk bsr chkOpt
lea ODsav,a0
beq.s .noOpt
cmp.b #otFetch,d1
beq.s .fpmF
cmp.b #otFPops,d1
blt.s .noOpt
cmp.b #otFPend,d1
bge.s .noOpt
; Yes, we'll do it!
.fpmDoit
addq #4,a6
bra fpChain
; A fetch preceded.
.fpmF btst #flFP,opFlags ; Was it an FP operand?
beq.s .noOpt ; No
addq #4,a6 ; Yes
backDP
lea ODnew,a1
move.b #stkPush,opMode(a1)
bsr OP2
bra fpPsh
; ===================
; FPcmp_h handles floating-point compares.
hndlr FPcmp_h,4 ; FPcmp_h
loc
get.l useFPUq,d0 ; Are we compiling FPU code?
bne.s .fcFPU
.noOpt addq.l #2,(a6) ; No - skip the opcode and just compile
bsr CompJSR ; a call to what follows there.
bra .fpcPsh ; Push descriptor
.fcFPU clr.b FPA-hbase(a4)
clr.b RCond-hbase(a4)
bsr saveOD
pop.l a1
move.w (a1),(a0) ; Set up comparison desc in ODnew
bsr CompFCMP
moveq #0,d0
move.w #$F240,d0 ; FScc opcode
swap d0
move.b condition,d0
move.b RCond,d1
clr.b RCond-hbase(a4) ; An extra clear never hurt anyone
eor.b d1,d0
lea int2FPconditions,a0
move.b 0(a0,d0.w),d0
push.l d0 ; Compile:
jsr comma ; FScc
push.l #$49C02D00 ; extb.l d0
jsr comma ; push.l d0
.fpcPsh lea ODnew,a0
bra PushOD
; ===================
hndlr shift_h,4 ; Handler for LSHIFT, RSHIFT
addq.l #4,(a6) ; Skip xinfo flag bytes
jsr length
pop.l d4 ; Save opcode in D4
bsr saveOD
lea ODsav,a0 ; Look at previous op (shift count)
cmp.b #otFetch,(a0) ; Is it a fetch?
bne.s .sh1 ; No: don't optimize
cmp.b #mdLit,opMode
bne.s .sh1 ; Or if it isn't literal
cmp.l #8,opLit
bgt.s .sh1 ; Or if count > 8 (limit for literal shifts)
tst.w opLit
bmi.s .sh1 ; Or negative
backDP ; Otherwise we'll optimize.
; Optimizing a shift means absorbing the constant in the shift op, which
; makes it in effect become a monadic operation. But it isn't like other
; monadics, since it can only operate on a D reg. We take care of this
; in OP2.
addq #4,a6 ; Drop cfa
move.l opLit,d5 ; Get literal value to D5
bsr popOD ; drop the literal desc
lea ODnew,a0
move.w d4,(a0) ; Set new desc type and mode
move.b #stk,opMode
tst.b d4 ; We use a -ve shift cnt to mean right
beq.s .sh0
neg.b d5
.sh0 move.b d5,opShiftCnt
move.b d5,shiftCnt-hbase(a4)
st pmOptFlg-hbase(A4) ; set up for going to pm_h
sf pmRevFlg-hbase(A4)
move.b #stk,pmChnFlg-hbase(A4)
move.b opType,operation-hbase(a4)
bra pmSetupDone
.sh1 bra compJSR ; can't opt - compile the JSR
hndlr bit_h,4 ; Handler for BSET, BRESET, BTOGGLE
addq.l #4,(a6) ; and BTEST
jsr length
pop.l d4 ; Opcode to D4
bsr saveOD
lea ODsav,a0 ; Look at previous op (bit offset #)
cmp.b #otFetch,(a0) ; Is it a fetch?
bne .b1 ; No: don't optimize
cmp.b #mdLit,opMode ; Yes: is it literal?
bne .b1 ; No: don't optimize
backDP ; Yes: we'll optimize.
addq #4,a6 ; Drop cfa
move.l opLit,d5 ; Get literal value to D5
move.l d5,d6
lsr.l #3,d6 ; Byte offset value to D6
downOD ; Look at prev op (base addr)
cmp.b #otFetch,(a0) ; Fetch?
bne.s .popA0 ; No: compile a pop to A0
backDP ; Yes
tst.b opind ; Is it an addr fetch?
bne.s .f2a ; No
move.l d6,d0 ; Yes - we'll recompile it as
bsr offsetAddr ; the appropriate bit op. Adjust addr
lea ODnew,a1
bsr moveDesc ; Move the desc to ODnew since we
lea ODnew,a0 ; might want to push it at the end
bra.s .bop
.f2a moveq #AnReg,d0 ; Addr wasn't an addr fetch.
bsr FetchToA ; Recompile fetch to A0.
upOD
bra.s .b0 ; Set (a0) as address
.popA0 compop xPopA0 ; No fetch descriptor for base address.
.b0 lea ODnew,A0 ; We come here if we need to make (a0) the
move.b #mdBD,opMode ; base address.
move.b #AnReg,opBreg
move.l d6,opDispl
.bop move.w d4,(a0) ; We enter here if the addr is OK already
move.w #$0800,d0 ; Static bit op opcode
and.w #$3,d4
move.w d4,d1
lsl.w #6,d1
or.w d1,d0 ; Insert op type (BTST, BCLR or whatever)
bsr EAbits
swap d0
and.w #$7,d5 ; Put bit number in
move.w d5,d0
push.l d0
jsr comma ; Compile operation
bsr CompExt
tst.b d4 ; Is it BTST?
bne.s .out ; No: we're finished
get.L xPushBool,-(a6) ; Yes: compile a JSR to the pushBool routine
bsr comma ; to get a boolean flag onto the stack
bsr pushOD ; And in this case we push the desc so that
.out rts ; a following IF can optimize.
.b1 bsr compJSR ; No optimization. Compile JSR to bit op
rts ; routine
; ====================
; OPTADDR
; ====================
; We handle address optimizations in two places. First, if we get an address
; fetch (that is a fetch with opind zero), followed by some arithmetic, we
; detect this in PM_H and try various optimizations. See the comments there,
; at label .pmAd .
;
; Secondly, something may be used as an address (via @ or !) which didn't
; have an address fetch component within it - e.g. if an address is grabbed
; out of a value etc. If this quantity then has a literal added to or subtracted
; from it, we could absorb this operation within the address. PM_H can't know
; about this, so we handle it here.
;
; This routine is called from at_h and store_h, if the preceding op is + or -.
; Here we check if the descriptor before that indicates a literal operand.
; If so, we absorb it.
;
; Assumes the add/sub descriptor is in ODsav, and the descriptor for the
; fetch or store in ODnew. A0 isn't preserved.
LitSub byte
LitChained
byte
align
optAddr loc
lea ODsav,a0
cmp.b #otSUB,(A0)
seq LitSub-hbase(A4) ; Set flag if op is SUB
tst.b opToFrom
sge LitChained-hbase(a4) ; Set flag if it's a chained op
DownOD ; Look at preceding op
cmp.b #otFetch,(a0) ; Fetch?
bne .no ; No
cmp.b #mdLit,opMode ; Yes: literal?
bne .oaNotLit ; No
; Literal precedes. We can optimize.
.oaLit move.l opLit,d6 ; Save lit value
BackDP
tst.b LitSub-hbase(a4)
beq.s .oa1
neg.l d6 ; Negate lit value if op is SUB
.oa1 push.l a0
lea ODnew,a0
move.b #mdBD,opMode ; Mode = BD
add.l d6,opDispl ; Adjust displacement by lit value
pop.l a0
DownOD ; Look at preceding op
cmp.b #otFetch,(A0) ; Fetch?
beq.s .oa3 ; Yes. We can disregard chaining flag,
; since we're going to recompile the
; whole thing anyway.
tst.b LitChained-hbase(a4) ; No. Was op chained?
beq.s .oa2 ; No.
compop xMvD1A0 ; Yes - addr will be in D1 - compile
; a move to A0
bra.s .clrbr ; Set base reg to A0, and out.
.oa2 lea ODnew,a0 ; No. Leave base reg as stack - this
bra.s .rsdp ; will result in a pop to A0 being
; compiled before the fetch/store.
.oa3 cmp.b #mdDn,opMode ; Two fetches. Is the first Dn?
beq.s .oaD ; Yes
BackDP ; No. Recompile fetch to go to A0
moveq #0,d0
bsr FetchToA
.clrbr lea ODnew,A0 ; Finally we fix ODnew descriptor:
move.b #AnReg,opBreg ; Base reg = A0
.rsdp ResetDP ; Mark new DP posn in desc, & can't opt back
rts
; We have the sequence Dn <lit> +/- @/!
; We'll set Dn as the base for the ODnew desc (which is already BD mode).
; Then LoadBase can take it from there. Doing it this way means that we
; we can opt back to a preceding descriptor, since the new BD descriptor
; completely describes the address.
.oaD backDP
move.b opReg,d0
lea ODnew,a0
markDP
move.b #mdBD,opMode
move.b d0,opBreg
bsr dropOD ; Drop the +/- desc
bsr dropOD ; and the <lit>
bsr dropOD ; and the Dn
rts
; A fetch preceded, but it wasn't a literal. Here we check if the preceding
; descriptor indicates a literal fetch. If so, and the operation is add, we
; swap the descriptors so the above code can optimize it.
.oaNotLit
tst.b LitSub-hbase(a4)
bne.s .no ; Out if not add
downOD
cmp.b #otFetch,(a0)
bne.s .no ; or if not a fetch
cmp.b #mdLit,opMode
bne.s .no ; or if not literal
backDP ; Right, we can do it.
move.l a0,a1
upOD
bsr exgOD ; Swap the descriptors
downOD
get.L DP,opDP
bsr compFetch ; Recompile non-literal fetch
upOD
bra.s .oaLit ; and back to the above code to absorb lit.
.no lea ODsav,a0 ; Preceding op was not a literal.
; We recompile the ADD or SUB to A0, which
moveq #AnReg,d0 ; we can do since ADDA and SUBA exist.
bsr op2Reg
cmp.b #AnReg,d0 ; Got it in A0?
beq.s .clrbr ; Yes - fix descriptor and out.
or.w #$2040,d0 ; No. Compile move.l dn,a0 first.
push.l d0
jsr wcomma
bra.s .clrbr
SavOptCode long
SavLen word
; ====================
hndlr at_h,4 ; at_h
addq.l #4,(a6) ; Skip xinfo flag bytes
jsr length
bsr SaveOD
pop.l D0
move.w D0,(A0) ; Set type and subtype from caller
pop.l A1 ; and flags
move.w (A1),D0
move.b D0,opFlags
move.b #stkPush,opToFrom ; Dest = stack
move.b #mdBD,opMode ; Mode = base-displacement
move.b #stkPop,opBreg ; Base reg = stack
move.b #1,opind ; Memory operand (displ = 0)
atChkOpt bsr ChkOpt ; Previous op?
beq .atNo
cmp.b #otFetch,D1
beq.s .fAt ; If fetch
cmp.b #otADD,D1
blt .atNo
cmp.b #otSUB,D1
bgt .atNo
bsr optAddr ; + or -
bra.s .at0
; Optimize fetch @. We absorb the fetch if possible.
; Note: A0 -> ODnew.
.fAt LEA ODsav,A1
CMP.B #Lcode,opSize(A1)
BNE.S .atNo
.fat1 BSR popODts
backDP
CMPI.B #mdLit,opMode
BNE.S .atind
MOVE.B #mdAbs,opMode ; Change Literal mode to Absolute
CLR.B opBreg
.atind ADDQ.B #1,opind ; For all other modes we just
; increment the indirection count
.at0
fChk lea ODsav,a0
lea ODnew,a1
cmp.b #otStore,(A0) ; Was prev op a store?
bne.s .at1 ; No
cmp.b #stkPop,opToFrom ; Yes. Was the stack popped?
bne.s .at1 ; No
bsr CmpAddrs ; Yes. Is it the same operand?
bne.s .at1
backDP ; Yes. we'll just recompile the
move.b #stk,opToFrom ; store without popping the stack.
bsr CompStore
ODvalid
rts
.at1 LEA ODnew,A0
BSR CompFetch
BSR PushOD
RTS
.atNo CLR.W ODsav-hbase(A4) ; We come here if we can't optimize.
BRA.S .at0 ; We push the desc but mustn't try
; to opt any further back.
hndlr store_h,2 ; store_h
addq.l #4,(a6) ; Skip xinfo flag bytes
pop.l A0
moveq #0,D0
move.w (A0),D0
bsr SaveOD
move.w D0,(A0) ; Set type and subtype from caller
move.b #stkPop,opToFrom ; Source = stack
move.b #mdBD,opMode ; Mode = base-displacement
move.b #stkPop,opBreg ; Base reg = stack
move.b #1,opind ; Memory operand (displ = 0)
stChkOpt bsr ChkOpt ; Previous op?
beq .stNo
cmp.b #otFetch,D1
beq .ftst ; If fetch
lea ODsav,A0
cmp.b #otSWAP,D1
beq.s .swapSt ; If SWAP
cmp.b #otOVER,D1
beq.s .overSt ; If OVER
cmp.b #otADD,d1
blt .stNo
cmp.b #otSUB,d1
bgt .stNo
bsr optAddr ; + or -
lea ODnew,a0
bra stChk
.overSt BackDP
PUSH.L x2ndToA0
JSR comma
LEA ODnew,A0
move.b #AnReg,opBreg
BRA CompStore
.swapSt BackDP
compop xpopD2
LEA ODnew,A0
MOVE.B #2,opToFrom
BRA CompStore
.compSt
.stNo lea ODnew,a0
bsr CompStore
.stEnd lea ODnew,a0
cmp.b #otStore,operation-hbase(a4)
beq pushOD
move.b #otOp2M,(a0)
bra pushOD
; Optimize fetch, ! (or w! or +! or whatever).
; Note: A0 -> ODnew.
.ftst lea ODsav,A1
cmp.B #Lcode,opSize(A1)
bne.S .stNo
.fst1 bsr popODts ; Combine fetch & store
backDP ; descriptors in ODnew
move.b #stkPop,opToFrom
cmpi.b #mdLit,opMode
beq.s .stLit
addq.b #1,opind
bra.s .st1
.stLit move.b #mdAbs,opMode
clr.b opBreg
; We come here to Stchk from a number of places, when we are doing a store
; according to the ODnew descriptor. This can sometimes be optimized,
; depending on what earlier descriptors we find.
stchk
.st1 downOD ; Look at previous desc
.stReChk lea ODnew,a1
move.b (a0),d0
cmp.b #otFetch,d0
beq .mm ; If a fetch, we'll move mem to mem
cmp.b #otDup,d0
beq .stDup ; If DUP
cmp.b #otCmp,d0
beq .stCmp ; If a comparison
cmp.b #otBit,d0
beq .btest ; If BTEST
cmp.b #otFPops,d0
blt.s .st2
cmp.b #otFPend,d0
blt .stFP ; If a floating-point op
.st2 cmp.b #otPMops,d0
blt .compSt ; If not integer op, just do the store
cmp.b #otPMend,d0
bge .compSt
; The previous operation is an integer arithmetic/logical op.
cmp.b #otMon,d0
bge .stMon ; If monadic
; It's a dyadic op. If destination is Dn direct, and this is a straight
; store (not ++> etc.) we use that reg as the work reg for the arithmetic.
; Otherwise we use D1 and then store D1 to the destination.
; NOTE: Don't ever include An direct here as a work reg for add or sub,
; although we do for fetches. If we did that, we could have an A reg
; conflict, since A0 can be used as a work reg for arithmetic for a fetch,
; and in that case A1 could be in use as a work reg for LoadBase for the
; same fetch (if we chain an arith op with an operand from memory).
; Thus we'd have no available A reg. It's not worth bothering to
; specifically check for this case, which is rather weird anyway.
.stpm0 CMP.B #mdDn,opMode(A1) ; Is destination Dn direct?
BNE.S .st3
CMP.B #1,opind(A1)
BNE.S .st3
CMP.B #otStore,(A1)
BNE.S .st3 ; and a straight store?
MOVEQ #0,D6 ; Yes - we'll use Dn as an operand reg
MOVE.B opReg(A1),D6
BRA.S .stpm1
.st3 MOVEQ #1,D6 ; No - we'll use D1 as a temporary
.stpm1 MOVE.L D6,D0
BSR op2Reg ; Recompile op to Dn
CMP.B D0,D6
BNE.S .stpmMv
CMP.B #1,D6
BNE.S .stpm2 ; and, if necessary:
.stpmMv LEA ODnew,A0
markDP
MOVE.B #1,opToFrom ; MOVE.x D1,<dest ea>
BRA .compSt ; (Dn above will have been D1 in this case)
.stpm2 MOVE.B D6,opToFrom(A1)
MOVE.L A1,A0
markDP
bra .stEnd
; Preceding op was a monadic arith/logical op. We check for the case where
; there is a preceding fetch specifying the same operand as the destination.
; In this case we can operate straight on the destination. Otherwise we go
; back to stpm0 and handle it as for the dyadic ops. We also do this for
; constant shifts, since we can't do these directly in memory. (All right,
; I know we can do a word shift of one place in memory, but this is
; pretty well useless in the Mops environment, so we don't bother about it.)
.stMon move.b d0,operation-hbase(a4) ; Ready for OP2 if needed
move.b opShiftCnt,shiftCnt-hbase(a4)
DownOD
cmp.b #otFetch,(a0)
bne.s .stmNo
bsr CmpAddrs
bne.s .stmNo
cmp.b #otSHIFT,operation-hbase(a4)
beq.s .stmNo
backDP
exg a0,a1
markDP
bsr newOD
moveq #1,d0
bsr LoadBase
exg a0,a1
bsr OP2
bsr releaseOD
bra .stEnd
.stmNo upOD
bra .stpm0
; Preceding op was DUP.
.stDup BackDP ; Wipe out the DUP
LEA ODnew,A0 ; - we just don't pop when we store.
MOVE.B #stk,opToFrom
get.L DP,opDP ; Put right DP value in descriptor
BRA .compSt
; Preceding op was Fetch.
.mm BackDP
cmp.b #mdLit,opMode ; Was it a literal?
beq.s .litSt ; Yes
.mm2 MOVE.L A0,A1 ; Save A0 across LoadBase call
MOVEQ #0,D0
BSR LoadBase ; Load base for source and set A0 desc
EXG A0,A1
upOD
MOVEQ #1,D0
st StoreFlg-hbase(a4)
BSR LoadBase ; Load base for dest, specifying A1
move.b #fromMem,opToFrom ; Mark as mem to mem store
EXG A0,A1 ; A0 -> src desc, A1 -> dest
BSR CompStore1
BRA .stEnd
.litSt ; We're storing a literal. If it's a -1
; being stored in a byte, we can use ST
lea ODnew,A1
cmp.b #Ccode,opSize(A1)
bne.s .mm2 ; If not a byte store, don't opt
cmp.l #-1,opLit
bne.s .mm2 ; If lit not -1, don't opt
move.w #$50C0,D0
lea ODnew,A0
bra CompMop2 ; Compile ST <ea>
; Preceding op was a comparison. If this is a byte store, we can
; optimize this to an Scc instruction.
.stCmp LEA ODnew,A1
CMP.B #Ccode,opSize(A1)
BNE .compSt ; If not a byte store, don't opt
CLR.B Rcond-hbase(A4)
BSR optCMP ; Optimize the CMP
lea ODsav,a0
cmp.b #otCmp,(a0)
bne .stReChk ; If CMP desc has vanished altogether, go back
lea ODnew,a0
bsr CompScc
bra .stEnd
; Preceding op was a BTEST. This is a bit like a comparison.
.btest lea ODnew,a1
cmp.b #Ccode,opSize(a1)
bne .compSt
geta DP,a0
subq.l #4,(a0)
move.w #$56C0,d0 ; Scc opcode is always SNE here
lea ODnew,a0
bsr CompMop2
bra .stEnd
; Preceding op was a floating-point op.
.stFP cmp.b #mdFPn,opMode(a1) ; Is store destination FPn?
bne.s .fp1
cmp.b #otStore,(a1) ; And is this a straight store?
bne.s .fp1
move.b opReg(a1),d6 ; Yes - we'll recompile the op there
bra.s .fp2
.fp1 moveq #1,d6 ; No - we'll use FP1 as a temporary
.fp2 move.l d6,d0
bsr FPop2reg ; Recompile the FP op to FPn
cmp.b d0,d6
bne.s .fp3
cmp.b #1,d6
bne.s .stpm2
.fp3 lea ODnew,A0 ; and, if necessary, move the temp FP1 to
markDP ; destination.
or.b #FPnReg,d0
move.b d0,opToFrom
bra .compSt
; ====================================
; TESTS, COMPARES etc.
; ====================================
ifFlg byte
align
DataFetch ; Returns with CC = EQ if A0 desc is a data
loc ; fetch - this includes literals, but not addr fetches.
CMP.B #otFetch,(A0)
BNE.S .out
CMP.B #mdLit,opMode
BEQ.S .out
TST.B opind
SEQ D0
TST.B D0
.out RTS
FPfetch ; Returns with CC = EQ if A0 desc is a
loc ; floating-point fetch.
cmp.b #otFetch,(a0)
bne.s .out
btst #flFP,opFlags
seq d0
tst.b d0
.out rts
; Ftch2TST converts the A0 descriptor (which must be a fetch) to a TST.
Ftch2TST
loc
moveq #0,d0
cmp.b #mdLit,opMode
beq CCmp
move.l a0,a1
downOD
cmp.b #otStore,(A0)
beq.s .cmpAd
cmp.b #otOp2M,(a0)
bne.s .f2tTST
.cmpAd BSR CmpAddrs
BNE.S .f2tTST ; If we just stored or operated to the same
upOD ; location, the CC will be OK already, so we
backDP ; omit the test altogether.
RTS
.f2tTST MOVE.L A1,A0 ; Restore appropriate desc ptr to A0
TST.B opind
BEQ.S .tstAddr
st ForceToR-hbase(a4)
st InhibitClr-hbase(a4)
clr.b opToFrom ; And compile a fetch to D0 (same effect as
bra CompFetch ; a test, but PC-rel mode works).
.tstAddr
MOVEQ #0,D0
BSR CompLEA
compop xTSTA0
RTS
; FPftch2TST is the floating-point equivalent of Ftch2TST. We don't worry
; about as many optimizations, which shouldn't matter here. All we do is
; recompile the fetch to go to FP0, since this sets the FCC and is about the
; same speed as a FTST. But especially, we already have code around to do it!
FPftch2TST
moveq #0,d0
bra CompMoveToFPn ; Easy, wasn't it?
; Ftch2CMP converts the A0 descriptor (which must be a fetch) to a CMP.
Ftch2CMP
MOVEQ #0,D0
BSR LoadBase
MOVE.W #$B000,D0
BRA CompMOp
; CompTST is called to compile a TST on the top of the stack. We check
; for a few optimization possibilities. Entered with A0 -> appropriate
; descriptor.
CompTST
move.b (a0),d0
CMP.B #otDUP,d0 ; Was last op DUP?
BEQ.S .ctDup ; Yes
CMP.B #otStore,d0 ; No. Was it Store?
beq.s .ctSt ; Yes
cmp.b #otPMops,d0 ; No. Was it an integer arith op?
blt.s .ctTSTpop ; If not, compile normal TST with pop.
cmp.b #otPMend,d0
bge.s .ctTSTpop
; Last op was an integer arith op.
moveq #1,d0
bra op2reg ; Recompile op to D1 - CC will be OK
; and that's all, folks!
; Last op was a store.
.ctSt CMP.B #stk,opToFrom ; Was it an unpopped stack store?
BNE.S .ctTSTpop ; No
backDP ; Yes, so it's the same operand, and
MOVE.B #stkPop,opToFrom ; CC will be OK. So we recompile the
BRA CompStore ; store with a pop, and that's all.
; Last op was DUP.
.ctDup backDP ; Omit it
downOD ; Look at prev op
CMP.B #otStore,(A0) ; Did it leave the run-time CC ok?
BLE.S .ctTST
CMP.B #otCCok,(A0)
BLE.S .ctRtn ; Yes - no need to test, and no stack
; effect. So we don't compile anything.
.ctTST compop xTSTstk ; No - TST stack without pop
RTS
.ctTSTpop
compop xTSTstkPop ; Normal TST with pop.
.ctRtn RTS
; CompFTST is called to compile a floating-point test on the top of the stack.
; The result will be in the floating condition code. As for CompTST, we check
; for optimization possibilities.
; Entered with A0 -> appropriate descriptor.
CompFTST
move.b (a0),d0
cmp.b #otFPops,d0 ; Was last op an FP arith op?
blt.s .cftTSTpop ; If not, compile normal FTST with pop.
cmp.b #otFPend,d0
bge.s .cftTSTpop
; Last op was an FP arith op.
BackDP
moveq #1,d0
bra FPop2reg ; Recompile op to FP1 - FCC will be OK
; and that's all, folks!
.cftTSTpop
moveq #0,d0 ; Rather than compile a FTST, we just
bra CompPopFPn ; pop the top operand to FP0. That is easier,
; it sets the FCC just as well, and is close
; to the same speed.
; ========================
; Comparisons.
; When we get a comparison op, we just push a descriptor and don't worry
; about optimization straight away. This is because it's a bit fiddly to
; generate a boolean flag on the stack (needing an Scc, two EXT instructions
; and a move) so in the unoptimized case we just call a subroutine.
; But if the following op just needs the condition code, we can bypass
; the generation of the boolean, which means that we can compile inline code.
; So, when we get a suitable following op, and we look down and see the
; comparison descriptor, we call OptCMP to generate optimized inline code for
; the comparison, replacing the default subroutine call.
; Entered with A0 -> comparison descriptor.
; The order of the operands is a bit tricky. For normal arithmetic operations,
; when we call OP2, A0 points to the source descriptor and A1 to the destination.
; This results in the operands being the other way around to the Forth order - if
; we have a b - we need to subtract b from a, so when we call OP2, A0 will point
; to the b descriptor and A1 to the a descriptor.
; Therefore, to be consistent, we do the same thing for comparisons. Thus if we
; have a b > we need to call OP2 with A0 -> b and A1 -> a. As comparisons
; don't store a result, it will sometimes be easier to call OP2 with the descriptors
; the other way around, as this won't mess anything up. In this case, we will call
; RevCond to reverse the test condition. In practice it isn't always easy to keep
; track of when to call RevCond, and we've resorted to good old trial and error on
; a few occasions!!
optCMP
loc
.loop BackDP
move.b #otCMP,operation-hbase(A4)
move.l a0,CMPdesc-hbase(a4) ; Save desc ptr for RevCond
MOVE.B 1(A0),D2 ; Subtype code (comparison type) to D2
MOVEQ #$F,D0
AND.B D2,D0
MOVE.B D0,condition-hbase(A4)
CMP.B #$F,D2 ; 2-op or 1-op?
BLE.S .2op ; If 2-op
; One operand - e.g. 0>
CMP.W #tsZNE,(A0)
beq.s .zne ; If this comp is 0<>
DownOD
.chkDF bsr DataFetch ; Was prev op a data fetch?
beq.s .ftch ; Yes - convert to test
bra compTST ; No - compile test
.zne DownOD
cmp.b #otCMP,(a0) ; This op was 0<>. Prev op another compare?
beq.s .cmp1st ; Yes - drop the 0<>
cmp.b #otFetch,(a0) ; If it is a literal fetch of -1 or 0,
bne compTST ; we likewise drop the 0<>. Otherwise
cmp.b #mdLit,opMode ; we compile a test.
bne.s .ftch
cmp.l #-1,opLit
beq.s .Lit1st
tst.l opLit
bne.s .ftch
.Lit1st BackDP
bra DropOD
.cmp1st bsr dropOD ; Compare followed by 0<>. Drop the latter
move.l CMPdesc,a0
bra .loop ; and loop.
.ftch BackDP ; Prev op was a fetch
BRA Ftch2TST ; Convert it to a TST.
; 2 operand compare, e.g. >
.2op downOD
BSR DataFetch ; Was prev op a data fetch?
BEQ.S .f2 ; Yes
.2op1 CMPI.B #otOVER,(A0) ; No. OVER?
BNE .2comp ; No. Just optimize the compare.
; We have OVER followed by a 2-op compare. This will happen in a
; CASE ... OF construction, so it is worth optimizing.
BackDP ; We'll absorb the OVER somehow.
DownOD
CMP.B #otFetch,(A0) ; Was previous op a fetch? (any fetch OK
; here)
BNE .ovcmp ; No
BackDP ; Yes
.nopop move.l a0,a1
UseODsrc ; Set stack not to pop
exg a0,a1
MOVE.B #stk,opMode(A1) ; Stack operand is really the "b" operand
moveq #0,d0
bsr LoadBase ; LoadBase for the fetched operand (the "a"
; operand)
exg a0,a1 ; We need a0 -> b, a1 -> a (see introduction)
bra OP2
.ovcmp compop xpopD2 ; pop.l d2
compop xcmpD2 ; cmp.l (a6),d2
rts ; Working out why we don't need to call RevCond
; is decidedly non-trivial! But it's true!!
; (Actually I found out by trial and error)
; We have a fetch followed by a 2-op compare
.f2 backDP ; Back the DP to the fetch
downOD
bsr DataFetch ; Was op before that a data fetch?
beq.s .ff ; Yes
.f21 cmp.b #otDup,(a0) ; Was it DUP?
beq.s .df ; Yes
cmp.b #otPMops,(a0)
blt .fcmp ; If not an integer arithmetic op
cmp.b #otPMend,(a0)
bge .fcmp
; We have <integer op>, fetch, compare. We recompile the integer op to D1.
; We then call OP2 with A0 -> fetch desc, A1 -> D1 desc. This is the "right" way
; around, so we don't need to call RevCond.
push.l a0 ; as needed for Op2Reg
move.b operation,d0
push.w d0
moveq #1,d0
bsr Op2reg ; Recompile preceding op to D1
pop.w d1
move.b d1,operation-hbase(a4)
bsr newClrOD
move.b #mdDn,opMode
move.b d0,opReg
move.l a0,a1
pop.l a0
upOD
moveq #0,d0
bsr LoadBase
bsr OP2
bsr releaseOD
rts
; We have DUP, fetch, compare.
.df backDP ; Absorb the DUP and set the stack
upOD ; not to pop. This is like fetch OVER compare
BSR RevCond ; but the operands are reversed.
BRA.S .nopop
; Optimize a fetch, fetch, compare sequence.
.ff BackDP
MOVE.L A0,A1 ; Save A0 across LoadBase call
MOVEQ #0,D0
BSR LoadBase ; Load base for "a"
EXG A0,A1
upOD
MOVEQ #1,D0
BSR LoadBase ; Load base for "b"
; A0 -> "b", A1 -> "a" - the "right" way around
BRA OP2
; Optimize just a fetch, compare sequence. The stack is the "a" operand, and
; the fetch is "b". We'll call OP2 with A1 -> "b", since OP2 will then recompile
; the fetch to Dn. This is the "wrong" way around, so we'll call RevCond.
.fcmp UpOD
moveq #0,d0
bsr LoadBase ; LoadBase for the fetch
move.l a0,a1
UseODsrc
exg a0,a1
move.b #stkPop,opMode(a1)
BRA OP2
; Optimize just a 2-op compare - replace by CMPM.L (A6)+,(A6)+
; Note we can come here whatever descriptor A0 is pointing to. Note that these
; operands are the "right" way around.
.2comp compop xcmp ; 2-op - compile CMPM.L (A6)+,(A6)+
rts
; OptFCMP is the floating-point equivalent of OptCMP. At present we're not
; worrying about FDUP or FOVER optimization. It's better to use FP locals
; as much as possible, anyway.
; The entry point CompFCMP is called from FPcmp_h to compile a FCMP. (We don't
; do the equivalent in integer mode since we call a subroutine. But custom
; FPU code is a lot better than general code, so we use it if we can.)
; Entered with A0 -> comparison descriptor.
optFCMP
loc
BackDP
CompFCMP
clr.b FPA-hbase(a4)
move.b #otFPcmp,operation-hbase(A4)
move.l a0,CMPdesc-hbase(a4) ; Save desc ptr for RevCond
move.b 1(a0),d2 ; Subtype code (comparison type) to D2
moveq #$F,d0
and.b d2,d0
move.b d0,condition-hbase(a4)
cmp.b #$F,d2 ; 2-op or 1-op?
ble.s .2op ; If 2-op
; One operand - e.g. F0>
DownOD
bsr FPfetch ; Was prev op an FP fetch?
bne.s compFTST ; No - compile test
BackDP ; Yes - convert fetch to a test
bra FPftch2TST
; 2 operand compare, e.g. F>
.2op downOD
bsr FPfetch ; Was prev op an FP fetch?
bne .2comp ; No. Just compile the compare.
; We have a fetch followed by a 2-op compare.
.f2 backDP ; Back the DP to the fetch
downOD
bsr FPfetch ; Was op before that an FP fetch?
beq.s .ff ; Yes
cmp.b #otFPops,(a0)
blt .fcmp
cmp.b #otFPend,(a0)
bge .fcmp
; We have <floating-op>, fetch, compare. We recompile the FP op to an FP reg.
; We then call OP2 with A0 -> fetch desc, A1 -> FPn desc. This is the "right" way
; around, so we don't need to call RevCond.
push.l a0
move.b operation,d0
push.w d0
moveq #0,d0
bsr FPop2reg ; Recompile preceding op to FP0 or FP1
pop.w d1
move.b d1,operation-hbase(a4)
bsr newClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.b d0,opReg
move.l a0,a1
pop.l a0
upOD
moveq #0,d0
bsr LoadBase
bsr OP2
bsr releaseOD
rts
; We have fetch, fetch, compare. We avoid a RevCond call by calling OP2
; with the descriptors the "right" way around, unless the "b" (A0) operand turns
; out to be an FP reg, and the "a" operand (A1) isn't. In this case we avoid an
; extra FMOVE at run time by calling RevCond and reversing the descriptors.
.ff BackDP
move.l a0,a1 ; Save A0 across LoadBase call
moveq #0,d0
bsr LoadBase ; Load base for "a"
exg a0,a1
upOD
moveq #1,d0
bsr LoadBase ; Load base for "b"
cmp.b #mdFPn,opMode(a1) ; Is "a" FPn?
beq.s .ffop2 ; Yes - just call OP2
cmp.b #mdFPn,opMode(a0) ; No - is "b" FPn?
bne.s .ffop2 ; No - just call OP2
bsr RevCond ; Yes - call RevCond and reverse descriptors
exg a0,a1
.ffop2 bsr OP2
rts
; Optimize just a fetch, compare sequence. The stack is the "a" operand, and
; the fetch is "b". We'll call OP2 with A1 -> "b", since OP2 will then recompile
; the fetch to FP0 unless the operand is already in an FP reg.
; This is the "wrong" way around, so we call RevCond.
.fcmp UpOD
bsr RevCond
BackDP
moveq #0,d0
bsr LoadBase
move.l a0,a1
UseODsrc
move.b #stkPop,opMode
bra OP2
; We have just a 2-op compare. The TOS is "b" and the second cell is "a".
; There's no mem-to-mem floating compare, and as with all FP ops the "destination"
; operand of FCMP must be FPn.
; So we pop the TOS ("b") to FP0, then call OP2 with A0 (source) = stack ("a")
; and A1 (dest) = FP0 ("b"). This is the "wrong" way around, so we call RevCond.
.2comp bsr RevCond
st FPdispFlg-hbase(a4) ; 2 to dispose
moveq #0,d0
bsr CompPopFPn
bsr NewClrOD
move.b #mdFPn,opMode
move.b #fbFP,opFlags
move.l a0,a1
bsr NewClrOD
move.b #stkPop,opMode
bsr OP2
bsr releaseOD
bsr releaseOD
rts
; CompScc compiles an Scc instruction, following a comparison.
; The A0 descriptor gives the ea for the operation.
CompScc MOVE.B condition,D0
MOVE.B RCond,D1
EOR.B D1,D0 ; Alter test condition if nec
LSL #8,D0 ; Shift condition into position
OR.W #$50C0,D0 ; Form Scc opcode in D0
BRA CompMop2 ; Compile Scc <ea>
; (IF) ( b -- ) Handles IF and NIF as far as compiling the branch opcode.
; It checks if optimization is possible, by checking the previous descriptors
; for various possibilities. If optimization is possible, the code is
; recompiled appropriately, and the right Bcc is compiled.
; If no optimization is done, the code just generated remains unchanged,
; and a BEQ or BNE opcode is compiled.
; Note that we don't try to optimize if a preceding fetch is an address
; fetch, since a CMP can't be done on An.
;
; The passed-in boolean is false for NIF and true for IF - we use this flag
; to decide whether to invert the branch condition (IF produces a branch if
; the condition just tested is false).
;
; The flag CCmpFlg is left indicating if a test on a literal was done. We
; evaluate this condition at compile time, and leave the flag as follows:
;
; 0 normal
; 1 always branch
; 2 never branch
;
; For a forward "always branch" situation, >RESOLVE actually deletes the
; code being branched over (which could never be executed).
loc
pif move.b #6,condition-hbase(a4) ; Initial condition is "not equal"
moveq #1,d0
and.l (a6)+,d0
move.b d0,RCond-hbase(a4) ; Save: true for reverse condition (if)
st ifFlg-hbase(a4) ; Setting this flag shows conditional
bsr SaveOD ; compilation may be possible
lea ODsav,A0
bsr ChkOpt ; Any optimization possibilities?
beq.s .no ; No
cmp.b #otCmp,D1
beq.s .comp ; If comparison op
bsr DataFetch
beq.s .ftch ; If data fetch
cmp.b #otBit,D1
beq.s .btest ; If BTEST
cmp.b #otFPcmp,d1
beq.s .Fcomp ; If floating-point comparison op
cmp.b #otPMops,D1
blt.s .no
cmp.b #otPMend,D1
bge.s .no
; Last op is an integer arith op, including AND, OR and XOR.
moveq #1,d0
bsr op2reg ; Recompile op to D1
bra .getcode ; and the CC will be OK now.
; No optimization.
.no BSR CompTST
BRA .getcode
; Last op is a comparison.
.comp BSR optCMP ; Optimize it
BRA.S .getcode
; Last op is a floating-point comparison
.Fcomp bsr optFCMP ; Optimize it
sf d7 ; Clear flag so FBcc will be compiled,
bra.s .gc1 ; not Bcc
; Fetch - e.g. bloggs IF
.ftch BackDP
BSR Ftch2TST
BRA.S .getcode
.btest geta DP,a0
subq.l #4,(a0)
; We come here with correct opcode byte in Condition.
.getcode
st d7 ; Set flag to show normal Bcc to be
; compiled (not FBcc)
.gc1 sf ifFlg-hbase(a4) ; Clear conditional comp enabling flag
get.b CCmpFlg,d0
bne.s .pifEnd
moveq #0,d0
move.b condition,d0
move.b RCond,d1
clr.b RCond-hbase(a4)
eor.b d1,d0 ; Alter test condition if nec
tst.b d7 ; Bcc or FBcc?
beq.s .gcFBcc
; Bcc
or.b #$60,d0 ; Form Bcc opcode byte
lsl #8,d0 ; Shift opcode into position
.gcCom push.l d0 ; Compile it, return
jsr wcomma
.pifEnd rts
.gcFBcc ; FBcc to be compiled
lea int2FPconditions,a0
move.b 0(a0,d0.w),d0 ; Convert condition bits to FP equivalent
or.w #$F280,d0 ; FBcc opcode
bra.s .gcCom